2014-07-21 1 views
3

J'ai a piece of code où il apparaît, dans chaque test que j'ai exécuté, que les appels de fonction ont une quantité importante de temps système. Le code est une boucle serrée, effectuant une fonction très simple sur chaque élément d'un tableau (contenant 4-8 millions int s).Réduction de l'en-tête des appels de fonction Java

L'exécution du code, qui se compose principalement de

for (int y = 1; y < h; ++y) { 
    for (int x = 1; x < w; ++x) { 
     final int p = y * s + x; 
     n[p] = f.apply(d, s, x, y); 
    } 
} 

exécuter quelque chose comme

(final int[] d, final int s, final int x, final int y) -> { 
    final int p = s * y + x; 
    final int a = d[p] * 2 
       + d[p - 1] 
       + d[p + 1] 
       + d[p - s] 
       + d[p + s]; 
    return (1000 * (a + 500))/6000; 
}; 

sur différentes machines (mon ordinateur portable de travail, un W530 avec Core i7 3840QM, un serveur VM avec un noyau de un Xeon E5-1620, et un nœud Digital Ocean avec un noyau d'un CPU inconnu), je reçois à plusieurs reprises un coup de performance statistiquement significatif lors de l'appel d'une méthode vs inlining. Tous les tests ont été effectués sur Java 1.8.0_11 (Java Server HotSpot (TM) 64 bits).

machine de travail:

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  40.860  0.184 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  22.603  0.159 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  22.792  0.117 ops/s 

serveur dédié, VM:

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  40.685  0.224 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  16.077  0.113 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  23.827  0.088 ops/s 

DO VPS:

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  24.425  0.506 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  9.643  0.140 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  13.733  0.134 ops/s 

Tous acceptable la performance, mais je suis intéressé à comprendre pourquoi l'appel a des frais généraux si importants et ce qui peut être fait pour optimiser cela. Actuellement en expérimentation avec différents ensembles de paramètres.

Il serait difficile, mais théoriquement possible, d'incruster toutes les opérations potentielles. Pour près d'une augmentation de performance de 2x, potentiellement la peine, mais la maintenance serait un cauchemar.

Je ne sais pas s'il existe un moyen raisonnable de regrouper un ensemble de répétitions; la plupart des opérations prennent plusieurs entrées (inconnues de l'appelant) et produisent une seule sortie.

Quelles autres options ai-je pour minimiser les frais généraux et les performances en soirée?

+0

Pour votre information à des gens qui veulent répondre, la fonction 'f' est en fait une méthode d'interface normale, pas un' @ functionalInterface' – dkatzel

+0

@dkatzel C'est une erreur de ma part (cela fait partie d'un projet de jouer avec Java 8), bien que cela puisse être conservé si cela aide d'une certaine façon. Il est assez important que 'f' soit remplissable par une classe ou lambda. – ssube

Répondre

9

Un appel de méthode n'est pas un problème car les méthodes à chaud sont souvent intégrées. Un appel virtuel est un problème.

Dans votre code, le profileur de type est dupé par la méthode d'initialisation Image.random. Lorsque Image.process est compilé JIT pour la première fois, il est optimisé pour appeler random.nextInt(). Ainsi, les invocations suivantes de Image.process entraîneront l'échec du cache en ligne suivi d'un appel virtuel non optimisé à Shader.apply.

  1. Retirez un appel Image.process de la méthode d'initialisation et JIT alors inline les appels utiles à Shader.apply.

  2. Après BlurShader.apply est inline vous pouvez aider JIT pour effectuer l'optimisation Common subexpression elimination en remplaçant

    final int p = s * y + x; 
    

    avec

    final int p = y * s + x; 
    

    Cette dernière expression est également rencontré dans Image.process, donc JIT ne calcule pas la même expression deux fois.

Après avoir appliqué ces deux changements que j'ai obtenu la note de référence idéal:

Benchmark       Mode Samples   Mean Mean error Units 
s.ShaderBench.testProcessInline thrpt   5  36,483  1,255 ops/s 
s.ShaderBench.testProcessLambda thrpt   5  36,323  0,936 ops/s 
s.ShaderBench.testProcessProc  thrpt   5  36,163  1,421 ops/s 
+0

La raison pour laquelle une interface est impliquée et la méthode apply dans une autre classe est que cette partie est connectable et ne peut pas être insérée (manuellement). Pour l'exemple/benchmark, un seul filtre est implémenté. Empêcher 'Image.random' d'être inline semble utile. Est-il possible que la JVM conserve plusieurs versions de 'Image.process' avec différentes fonctions inline? Des normes ou des conventions à ce sujet? – ssube

+0

Pour plus de détails, j'espère pouvoir utiliser 4-6 couches ('Image's) de différentes tailles, chacune traitée une ou deux fois par seconde, chacune utilisant un filtre différent. J'essaie d'éviter de faire des sous-classes abstraites et d'écrire 4-6 'Image'. – ssube

+0

@ssube HotSpot JVM peut intégrer jusqu'à 2 implémentations d'une interface les plus fréquemment utilisées dans un site d'appel unique. Il y aura un appel de méthode virtuelle pour d'autres implémentations. C'est juste un comportement d'une JVM particulière, non couverte par une convention. Pour voir l'activité inline de JIT, utilisez les options JVM suivantes: '-XX: + PrintCompilation -XX: + UnlockDiagnosticVMOptions -XX: + PrintInlining'. – apangin

Questions connexes