46 votes

Alternative de classe de chaînes .net

Depuis que je suis la planification d'une application qui va contenir BEAUCOUP de ses données en mémoire, je voudrais avoir une sorte de "compact" de la chaîne de classe, au moins un qui va contenir la chaîne de caractères dans le format n'est pas plus grand que zéro résilié version ASCII de la chaîne.

Connaissez-vous une telle classe string de la mise en œuvre - il doit avoir une certaine utilité des fonctions comme l'original de la classe string.

EDIT:

J'ai besoin de trier les chaînes de caractères et être en mesure de numériser à travers eux, pour n'en citer que quelques-uns des opérations que je vais utiliser.

Idéalement, il devrait être compatible avec le Système.Chaîne, afin de base de recherche et de remplacement d'action permettrait d'optimiser la mémoire de l'application de l'empreinte.

NUMÉROS:

Je pourrais avoir 100k de chaque enregistrement ayant jusqu'à 10 chaîne ayant de 30 à 60 caractères. Donc:

100000x10x60=60000000=57mega caractères. Pourquoi ne pas avoir 60 mégas de ram utilisé à la place de 120 mégas de ram? Les opérations seront plus rapides, tout sera plus serré.

Les arbres seront utilisés pour la recherche, mais ne sera pas utile dans la regex scans que j'ai l'intention d'avoir.

51voto

Jon Skeet Points 692016

EDIT: j'ai maintenant un blog post sur ce sujet qui va dans une bonne quantité de plus de détails.


Aller par les chiffres de votre activité:

Je pourrais avoir 100k de chaque enregistrement ayant jusqu'à 10 chaîne ayant de 30 à 60 caractères.

Nous allons commencer par ajouter dans l'objet de frais généraux - une chaîne prend environ 20 octets (IIRC - peut-être plus sur une version 64 bits du CLR) , plus les données réelles, en raison de l'inévitable objet de la surcharge et de la longueur. Nous allons faire le calcul à nouveau:

L'aide de la chaîne: 1 million d'objets à 20+120 octets = 140MB

À l'aide d'une nouvelle classe: 1 million d'objets à 20+60 octets = 80 MO

Encore une 60MO différence, bien sûr, mais proportionnellement moins que vous attendiez. Vous êtes seulement d'économiser 42% de l'espace au lieu de 50%.

Maintenant, vous parlez de choses en étant plus rapide: étant donné que le CLR est nativement au courant de l' string, je soupçonne un tiers de la classe ne sera pas en mesure de correspondre à la vitesse de certaines de ses activités, et vous auriez à mettre beaucoup de travail pour obtenir beaucoup d'autres à la même vitesse. Certes vous permettra d' avoir une meilleure cohérence de cache, et si vous pouvez ignorer les questions culturelles, pour gagner un peu de temps à trop en faire toutes les comparaisons ordinales.

Pour l'amour de 60MO, je ne serais pas la peine. C'est une petite différence ces jours - examiner comment beaucoup plus de clients que vous aurez à acquérir par le biais de ce qui rend cette petite sauvegarde, afin de rattraper l' important coût supplémentaire de travailler avec deux différents types de chaînes.

Ayant dit tout cela, je suis assez tenté de mettre en œuvre moi-même de toute façon comme un projet de blogging comme Edulinq. N'attendez pas de résultats pour des semaines ou des mois bien :)

EDIT: je viens de pensé à un autre problème. Les chiffres que nous avons ci-dessus ne sont pas vraiment juste... parce que la classe string est spécial. Il incorpore ses données directement dans l'objet - à la différence de tout autre type de données en dehors de tableaux, de la taille d'un string de l'instance n'est pas fixe; il varie selon les données qu'il contient.

La rédaction de votre propre AsciiString classe, vous ne seriez pas en mesure de le faire - que vous auriez à intégrer un tableau de référence au sein de la classe:

public class AsciiString
{
    private readonly byte[] data;
}

Cela signifie que vous auriez besoin d'un supplément de 4 ou 8 octets pour la référence (32 ou 64 bits CLR) et la charge supplémentaire d'un tableau d'objet (16 octets, IIRC) par chaîne.

Si vous l'avez conçu comme Java, en prenant une sous-chaîne est possible de réutiliser l'existant tableau d'octets (deux chaînes pourraient partager), mais vous auriez besoin d'une longueur supplémentaire et un offset AsciiString. Vous pouvez également perdre de la cohérence de cache avantages.

Vous pourriez utiliser bruts des tableaux d'octets que la structure de données et d'écrire un tas de méthodes d'extension pour agir sur eux... mais ce serait horrible, comme à l'époque on ne pouvait pas faire la différence entre normal d'un tableau d'octets et un qui était censé représenter une chaîne de caractères ASCII.

Une autre possibilité serait de créer une structure comme ceci:

struct AsciiString
{
    private readonly byte[] data;
    ...
}

Que serait un moyen efficace de vous donner un typage fort à nouveau, mais vous devez penser à des choses comme:

AsciiString x = new AsciiString();

qui serait nulle data de référence. Vous pourriez traiter efficacement ce que si x ont une valeur nulle, mais il serait assez non-idiomatique.

13voto

NightDweller Points 702

En fait, j'ai eu un problème semblable, mais quelque peu différente problème de paramètres. Ma requête porte avec 2 types de chaînes - relativement court de mesure de 60 à 100 chars et de plus avec de 100 à 1000 octets (en moyenne autour de 300).

Mon cas d'utilisation doit également prendre en charge unicode texte, mais un pourcentage relativement faible des chaînes en fait non-anglais caractères.

Dans mon cas d'utilisation, j'ai été d'exposer chaque Chaîne de propriété en tant que natif de la Chaîne, mais la structure de données sous-jacente a été un byte[] holding unicode octets.

Mon cas d'utilisation exige également la recherche et le tri par le biais de ces chaînes, l'obtention de sous-chaînes et d'autres communes de la chaîne des opérations. Mon dataset mesures dans les millions.

La mise en œuvre de base ressemble à quelque chose comme ceci:

byte[] _myProperty;

public String MyProperty
{

   get 
   { 
        if (_myProperty== null)
            return null;

        return Encoding.UTF8.GetString(value);
   }

   set
   { 
        _myProperty = Encoding.UTF8.GetBytes(value);

   }

}

Les performances de ces conversions, même lorsque vous effectuez une recherche et de tri a été relativement faible (était d'environ 10 à 15%).

C'était bien un temps, mais je voulais réduire les frais généraux supplémentaires. La prochaine étape a été de créer un tableau fusionné pour toutes les chaînes de caractères dans un objet (un objet titulaire, soit: 1 court et 1 long chaîne de caractères, ou de 4 et 1 longue chaîne). donc, il y aurait un byte[] pour chaque objet, et ne nécessitent 1 octet pour chacune des chaînes (enregistrer leurs longueurs qui sont toujours < 256). même si vos chaînes peuvent être plus longs à 256, et l'int est encore moins cher que le de 12 à 16 octets de surcharge pour le byte[].

Cela réduit de beaucoup le byte[] les frais généraux, et ajouté un peu de complexité, mais aucun impact sur les autres à la performance (le codage de passe est relativement cher par rapport à la matrice de copie de jeu).

cette application ressemble à quelque chose comme ceci:

byte _property1;
byte _property2;
byte _proeprty3;

private byte[] _data; 

byte[] data;
//i actually used an Enum to indicate which property, but i am sure you get the idea
private int GetStartIndex(int propertyIndex)
{  

   int result = 0;
   switch(propertyIndex)
   {
       //the fallthrough is on purpose 
       case 2:
          result+=property2;
       case 1:
          result+=property1;

   }

   return result;
}

private int GetLength(int propertyIndex)
{
   switch (propertyIndex)
   {
     case 0:
        return _property1;
     case 1: 
        return _property2;
     case 2:
        return _property3;
   }
    return -1;
}

private String GetString(int propertyIndex)
{
   int startIndex = GetStartIndex(propertyIndex);
   int length = GetLength(propertyIndex);
   byte[] result = new byte[length];
   Array.Copy(data,startIndex,result,0,length);

   return Encoding.UTF8.GetString(result);

}

si la lecture ressemble à ceci:

public String Property1
{
   get{ return GetString(0);}
}

Le setter est dans le même état d'esprit, de copier les données d'origine dans deux tableaux (entre 0 commencer à startIndex, et entre startIndex+longueur de longueur) , et de créer un nouveau tableau avec les 3 tableaux (dataAtStart+NewData+EndData) et réglez la longueur de la matrice appropriée à la variable locale.

J'étais toujours pas satisfait de la mémoire enregistré, et le très difficile le travail de la mise en œuvre manuelle pour chaque propriété, alors j'ai construit une mémoire compresser système de pagination qui utilise incroyablement rapides QuickLZ pour compresser une pleine page. Cela m'a donné beaucoup de contrôle sur le temps-mémoire compromis (qui est essentiellement la taille de la page).

Le taux de compression pour mon cas d'utilisation (par rapport à la plus efficace byte[] magasin) approche les 50% (!). J'ai utilisé une taille de page de env 10 chaînes par page et groupées des propriétés similaires ensemble (qui ont tendance à avoir des données similaires). Cela a ajouté une surcharge supplémentaire de 10 à 20% (sur le haut de l'encodage/décodage de passage, qui n'est toujours nécessaire). Le mécanisme de pagination caches récemment accédé à des pages jusqu'à une taille configurable. Même sans la compression de cette mise en œuvre permet de définir un facteur fixe sur le rétroprojecteur pour chaque page. L'inconvénient majeur de mon actuel de la mise en œuvre de la cache de la page, c'est que la compression n'est pas thread-safe (sans elle, il n'y a pas ce problème).

Si vous êtes intéressé dans le comprimé mécanisme de pagination laissez-moi savoir (j'ai été à la recherche d'une excuse pour ouvrir la source).

6voto

ShuggyCoUk Points 24204

Suppléant structures de données

Je dirais que, compte tenu de votre désir aussi de rechercher par le biais de la stockées "chaîne de valeurs" vous devriez considérer si un Trie de la structure, comme une Patricia Trie ou, pour mieux la mémoire de l'amortissement, Dirigé Acyclique Mot Graphique (appelé affctionalty comme un DAWG) fonctionne mieux.

La Construction de leur prendra plus de temps (même si souvent, ils sont utilisés dans les cas où le stockage sous-jacent lui-même représente cette forme raisonnablement bien permettant la construction rapide à l'avant)et même si certaines opérations sont algorithmiquement, de qualité supérieure, vous pouvez découvrir que dans le monde réel de l'utilisation de choses sont en fait plus lentement qu'ils ne réduire de façon significative l'empreinte mémoire de vos données aussi longtemps que il ya une quantité raisonnable de répétition.

Ceux-ci peuvent être considérés comme des généralisations de l' (intégré) de duplification fourni dans .net (et java et beaucoup d'autres langues) de la chaîne de stage.

Si vous souhaitez conserver une commande de cordes avec certains lexicographiques manière (de sorte que vous devez considérer d'un caractère ou d'un code de point à temps), puis le Patricia Trie est probablement la meilleure option, la mise en œuvre de la passation de commande sur le dessus de la DAWG serait problématique.

Alternes, plus ésotérique des solutions peut fonctionner que si vous avez un domaine particulier de cordes, y compris:

run length encoding (encodage et d'autres formes de compression.

Le coût de l'accès aléatoire à une chaîne et le risque de l'utilisation de plus de mémoire si les entrées à son tour d'être pas comme on l'espérait. Le codage de Huffman a tendance à bien fonctionner sur un texte en anglais et est assez simple à réaliser, il a l'avantage que le dictionnaire pour peut être communiquées à travers toutes les entrées dans le jeu aussi longtemps que la distribution de fréquence des lettres est comparable. Tri deviendrait problématique de nouveau.

Chaînes de longueur fixe.

si vous connaissez les chaînes de caractères sont petits, et tous presque la même (ou le même) taille que vous pouvez stocker dans le fixe, les valeurs de la taille (même si les structures désiré si le nombre de caractères est dans la région de 16 ans ou de moins (ou de la limite d'utilisation ici dépendra de votre utilisation précise et peut être fortement dépendante de la volonté des vous de régler votre code pour jouer gentil avec cette conception)

5voto

James Black Points 26183

Vous pouvez créer une nouvelle structure de données pour la tenue de ces, mais je pense que c'est exagéré.

Mais, si vous avez un tableau de chaque mot ou expression commune, alors vous stocker l'index dans un tableau pour chaque mot.

Vous payez ensuite 4 octets pour chaque mot, mais si chaque mot est en moyenne de 3,6 caractères, puis vous enregistrez vous-même 3.2 octets pour chaque mot, en moyenne, puisque vous payez le 2 octets/lettre de pénalité une fois/mot.

Mais, pour faire des recherches ou de sortes que vous allez prendre un gros gain de performance par la reconstruction de la chaîne, au moins pour un court laps de temps.

Vous voudrez peut-être repenser la manière de concevoir votre programme, car il existe de nombreux programmes qui utilisent de grandes quantités de données et peut fonctionner que dans un nombre relativement restreint de la mémoire.

4voto

Eh bien, il y a le UTF8Encoding classe

//Example from MSDN
using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding enc = new UTF8Encoding(true, true);
      string value = "\u00C4 \uD802\u0033 \u00AE"; 

      try
      {
         byte[] bytes= enc.GetBytes(value);
         foreach (var byt in bytes)
            Console.Write("{0:X2} ", byt);
         Console.WriteLine();

         string value2 = enc.GetString(bytes);
         Console.WriteLine(value2);
      }
      catch (EncoderFallbackException e)
      {
         //Encoding error
      }                     
   }
}

Cependant, comme Jon dit, n'importe quand vous voulez l'utiliser avec n'importe quelle méthode qui attend une chaîne de caractères (la plupart de la .Net-library), vous devez convertir le retour à la normale chaîne unicode de toute façon... si vous nous avez donné plus d'informations à propos de ce que vous essayez de faire, on pourrait peut-être vous aider à trouver une meilleure solution?

Ou, si vous avez vraiment besoin de bas niveau de tableau d'octets non-internationalizable null chaînes, vous pourriez être mieux de l'écrire dans C++.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X