2010-06-07 4 views
2

Je tente de créer un bit de code bien optimisé pour créer un nombre de chiffres X de longueur (où X est lu à partir d'un fichier de propriétés d'exécution), basé sur un numéro de séquence généré par DB (Y), qui est ensuite utilisé un nom de dossier lors de l'enregistrement d'un fichier.Le moyen le plus rapide d'ajouter un nombre de chiffres à Java dans un certain nombre de chiffres

Je suis venu avec trois idées à ce jour, le plus rapide qui est le dernier, mais j'apprécierais tout les conseils peuvent avoir sur ce ...

1) instancier un StringBuilder avec initial capacité X. Ajouter Y. Pendant la longueur < X, insérer un zéro à pos zéro.

2) Instancier un StringBuilder avec la capacité initiale X. Pendant la longueur < X, ajoutez un zéro. Créez un DecimalFormat basé sur la valeur StringBuilder, puis formatez le nombre lorsque cela est nécessaire.

3) Créez un nouvel int de Math.pow (10, X) et ajoutez Y. Utilisez String.valueOf() sur le nouveau numéro, puis sous-chaîne (1) it.

La seconde peut évidemment être divisée en sections de boucle extérieure et intérieure.

Alors, des conseils? En utilisant une boucle de 10 000 itérations, je reçois des timings similaires à ceux des deux premiers, et la troisième méthode est environ dix fois plus rapide. Est-ce que cela semble correct?

code méthode de test complet ci-dessous ...

// Setup test variables 
    int numDigits = 9; 
    int testNumber = 724; 
    int numIterations = 10000; 
    String folderHolder = null; 
    DecimalFormat outputFormat = new DecimalFormat("#,##0"); 

    // StringBuilder test 
    long before = System.nanoTime(); 
    for (int i = 0; i < numIterations; i++) 
    { 
     StringBuilder sb = new StringBuilder(numDigits); 
     sb.append(testNumber); 
     while (sb.length() < numDigits) 
     { 
      sb.insert(0, 0); 
     } 

     folderHolder = sb.toString(); 
    } 
    long after = System.nanoTime(); 
    System.out.println("01: " + outputFormat.format(after - before) + " nanoseconds"); 
    System.out.println("Sanity check: Folder = \"" + folderHolder + "\""); 

    // DecimalFormat test 
    before = System.nanoTime(); 
    StringBuilder sb = new StringBuilder(numDigits); 
    while (sb.length() < numDigits) 
    { 
     sb.append(0); 
    } 
    DecimalFormat formatter = new DecimalFormat(sb.toString()); 
    for (int i = 0; i < numIterations; i++) 
    { 
     folderHolder = formatter.format(testNumber); 
    } 
    after = System.nanoTime(); 
    System.out.println("02: " + outputFormat.format(after - before) + " nanoseconds"); 
    System.out.println("Sanity check: Folder = \"" + folderHolder + "\""); 

    // Substring test 
    before = System.nanoTime(); 
    int baseNum = (int)Math.pow(10, numDigits); 
    for (int i = 0; i < numIterations; i++) 
    { 
     int newNum = baseNum + testNumber; 
     folderHolder = String.valueOf(newNum).substring(1); 
    } 
    after = System.nanoTime(); 
    System.out.println("03: " + outputFormat.format(after - before) + " nanoseconds"); 
    System.out.println("Sanity check: Folder = \"" + folderHolder + "\""); 
+2

Soyez prudent avec microbenchmarking la JVM: http://java.sun.com/docs/hotspot/HotSpotFAQ.html#benchmarking_simple – BalusC

+2

Votre code accède au * harddisk *, et vous vous inquiétez des performances de la mise en forme des chaînes pour la génération de noms de fichier? Sérieusement? –

+0

En fait, il va utiliser GPFS via WebDAV et/ou Amazon S3. ng pour optimiser tout ce que je peux, pendant que je construis la chose. De plus, c'est devenu un exercice académique pour moi aussi maintenant! – Martin

Répondre

7

j'arrêter de faire des optimisations à base de micro-benchmarks et aller pour quelque chose qui ressemble codewise élégant, comme String.format("%0"+numDigits+"d", testNumber)

+1

Tout cela a du sens, mais c'est plus lent selon le micro-benchmarking. Est-ce vraiment inutile, parce que j'obtiens des résultats qui suggèrent que le String.format est 100 fois plus lent que la sous-chaîne d'un int. Cela n'a-t-il aucune utilité? – Martin

+2

Vous devez être prudent lors de l'interprétation des figures de micro-benchmarks. Dans certaines situations, le compilateur/compilateur JIT optimise les gros morceaux de code s'il se rend compte que c'est inutile. Il est assez évident, par exemple, que (dans le troisième cas) 'newNum' restera le même dans toutes les itérations. Cela pourrait à son tour entraîner 'String.valueOf (newNum) .substring (1);' à être placé en dehors de la boucle, ce qui a pour effet d'invalider la totalité du benchmark. – aioobe

+0

J'ai créé un int [] de longueur 10 000, puis j'ai inséré un nombre aléatoire compris entre 0 et 10 000 dans chaque position et répété les tests, et les temps étaient presque identiques. Je suis fasciné par, et préfère de loin la lisibilité de la chaîne.le format moyen de créer le nombre, mais il est difficile d'ignorer les horaires: StringBuilder: 31,317,244 nanosecondes DecimalFormat: 36,368,634 nanosecondes sous-chaînes: 2,623,557 nanosecondes String.Format: 184,692,956 nanosecondes – Martin

1

Insertion des caractères de remplissage un par un est évidemment lent. Si la performance est vraiment un gros problème, vous pouvez utiliser des constantes de chaîne prédéfinies de lengts 1..n-1 à la place (où n est la plus grande longueur attendue), stockée dans une ArrayList aux index correspondants.

Si n est très grand, au moins vous pouvez toujours insérer en plus gros morceaux au lieu de simples caractères. Mais dans l'ensemble, comme d'autres l'ont souligné, l'optimisation n'est possible que si vous avez profilé votre application dans des circonstances réelles et trouvé quel morceau de code spécifique est le goulot d'étranglement. Ensuite, vous pouvez vous concentrer sur cela (et bien sûr sur le profil pour vérifier que vos changements améliorent effectivement les performances).

1

Utilisez String.format ("% 0 [longueur] d", i)

Pour une longueur de 8 ce serait

String out = String.format("%08d", i); 

Il est plus lent, mais le temps passé à taper et le débogage plus le code complexe dépassera probablement le temps total supplémentaire jamais utilisé pendant l'exécution. En fait, si vous additionnez toutes les heures-homme que vous avez déjà passées en revue, cela dépasse probablement de beaucoup le temps d'exécution.

0

lien This probably related discute beaucoup des façons de le faire.Je recommanderais l'option Apache, StringUtils, il peut ou non être le plus rapide absolu, mais c'est généralement l'un des plus faciles à comprendre, et il a été écrasé, donc il ne cassera probablement pas dans un cas de bord imprévu. ;)

1

Voici une solution qui est fondamentalement la même chose que votre StringBuilder avec deux optimisations:

  1. Il écrit directement à un tableau sans passer par la tête StringBuilder
  2. Il fait les opérations en sens inverse au lieu d'insert (0), qui requries un arraycopy chaque fois

Il fait également l'hypothèse que numDigits seront> = les caractères réels requis, mais traiter correctement les nombres négatifs:

before = System.nanoTime(); 
String arrString=null; 
for (int j = 0; j < numIterations; j++){ 
    char[] arrNum = new char[numDigits]; 
    int i = numDigits-1; 
    boolean neg = testNumber<0; 
    for(int tmp = neg?-testNumber:testNumber;tmp>0;tmp/=10){ 
    arrNum[i--] = (char)((tmp%10)+48); 
    } 
    while(i>=0){ 
    arrNum[i--]='0'; 
    } 
    if(neg)arrNum[0]='-'; 
    arrString = new String(arrNum); 
} 
after = System.nanoTime(); 
System.out.println("04: " + outputFormat.format(after - before) + " nanoseconds"); 
System.out.println("Sanity check: Folder = \"" + arrString + "\""); 

Ce bien la méthode surperformé vos échantillons sur ma machine pour les négatifs et était comparable pour les points positifs:

01: 18,090,933 nanoseconds 
Sanity check: Folder = "000000742" 
02: 22,659,205 nanoseconds 
Sanity check: Folder = "000000742" 
03: 2,309,949 nanoseconds 
Sanity check: Folder = "000000742" 
04: 6,380,892 nanoseconds 
Sanity check: Folder = "000000742" 

01: 14,933,369 nanoseconds 
Sanity check: Folder = "0000-2745" 
02: 21,685,158 nanoseconds 
Sanity check: Folder = "-000002745" 
03: 3,213,270 nanoseconds 
Sanity check: Folder = "99997255" 
04: 1,255,660 nanoseconds 
Sanity check: Folder = "-00002745" 

Edit: j'ai remarqué vos tests resued certains des objets dans la boucle d'itération, ce que je n'avais pas fait dans le mien (comme ne pas recalculer baseNum dans la version de sous-chaîne). Lorsque j'ai modifié les tests pour être cohérent (pas resuing tous les objets/calculs ma version de meilleures performances que le vôtre:

01: 18,377,935 nanoseconds 
Sanity check: Folder = "000000742" 
02: 69,443,911 nanoseconds 
Sanity check: Folder = "000000742" 
03: 6,410,263 nanoseconds 
Sanity check: Folder = "000000742" 
04: 996,622 nanoseconds 
Sanity check: Folder = "000000742" 

Bien sûr, comme d'autres l'ont mentionné micro étalonnage est incroyablement difficile/« fudge » avec tous l'optimisation effectuée Par la VM et l'incapacité de les contrôler

Questions connexes