27 votes

Pourquoi Array.slice est-il si (scandaleusement !) lent ?

Voici mon code de référence :

def bm(duration: Long)(f: => Unit)={
  val end = System.currentTimeMillis + duration
  var count = 0
  while(System.currentTimeMillis < end) { f; count += 1 }
  count
}

val array = new scala.util.Random().alphanumeric.take(1000).toArray

(1 to 20).map { _ => bm(1000) { array.slice(100,200) } }.sum / 20

En effectuant cette opération plusieurs fois, j'obtiens systématiquement des chiffres de l'ordre de 1,5 million de tranches par seconde. Entre 1,4 et 1,6.

Maintenant, je fais ça :

 implicit class FastSlicing(val a: Array[Char]) extends AnyVal {
   def fastSlice(from: Int, until: Int)  = Arrays.copyOfRange(a, from, until)
 }
 (1 to 20).map { _ => bm(1000) { array.fastSlice(100,200) } }.sum / 20

Et le résultat que j'obtiens est entre 16 et 18 millions de tranches par seconde. C'est plus de 10 fois plus rapide.

Maintenant, je connais tous les raisonnements habituels sur les compromis que scala fait pour fournir des idiomes fonctionnels et la sécurité de type parfois au détriment des performances .... Mais dans ce cas, je pense qu'ils échouent tous à répondre à une question simple : pourquoi ArrayOps.slice pas mis en œuvre de cette façon ? ?? Je réalise qu'il y aurait de multiples implémentations identiques nécessaires, à cause de la façon dont Java traite les tableaux primitifs, mais c'est tout au plus une gêne mineure, pas vraiment un problème du genre à justifier un gain de performance de 10x.

El .slice n'est qu'un exemple, la plupart des autres opérations de tableau semblent souffrir du même problème. Pourquoi faut-il qu'il en soit ainsi ?

Mise à jour Maintenant, voici quelque chose que je trouve encore plus choquant :

val seq = new scala.util.Random().alphanumeric.take(1000).toIndexedSeq
(1 to 20).map { _ => bm(1000) { seq.slice(100,200) } }.sum / 20

Cela fait environ 5-6 millions de tranches par seconde pour moi. Mais ça :

import scala.collections.JavaConversions._
(1 to 20).map { _ => bm(1000) { seq.subList(100,200) } }.sum / 20

fait entre 12 et 15 millions ! D'accord, ce n'est pas une différence d'ordre de grandeur, comme dans le cas des tableaux, mais (1) il n'y a pas de manipulation spéciale des primitives impliquées ici, donc ce serait complètement trivial à mettre en œuvre en utilisant l'outillage standard de Java, et (2) la collection est immuable ... comment est-il possible de renvoyer une référence à une plage d'indices ? ? ??

2voto

Zang MingJie Points 1839

Il a été corrigé dans scala 2.12 .

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