Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Est-ce toujours plus rapide d'exécuter des opérations dans la mémoire des machines ?
Non, selon des chercheurs

Le , par Michael Guilloux

0PARTAGES

6  10 
L'une des préoccupations majeures pour un développeur est de pouvoir livrer des logiciels qui offrent des performances adéquates pour les différentes opérations à exécuter. Même pour un informaticien « occasionnel », il est évident que l'accès répété au disque peut dégrader considérablement la performance de son logiciel en termes de temps d'exécution des opérations.

Depuis plusieurs années - surtout avec l'avènement des domaines d'application informatique tels que le High Performance Computing, le Big Data, et le BI - les développeurs ont été en quête de nouvelles possibilités d'accroître la vitesse de traitement de leurs applications. L'informatique in-memory ou en mémoire a donc contribué à la réalisation de cet objectif.

L'informatique in-memory désigne en fait le stockage de l'information dans la mémoire RAM des machines plutôt que d'opter pour des accès fréquents au disque, qui ont tendance à ralentir considérablement l'exécution des opérations.

Les opérations en mémoire comprennent notamment la mise en cache d'innombrables quantités de données en permanence. Cela garantit des temps de réponse extrêmement courts pour les recherches. Pour les sites web encore, elles offrent la possibilité de stocker les données de session pour une performance optimale du site.

La tradition recommande donc aux développeurs de minimiser l'accès au disque en effectuant autant que possible le travail en mémoire, pour bénéficier de temps d'exécution plus courts. Mais, est-ce toujours plus rapide d'exécuter des opérations dans la mémoire des machines ? Non, selon une étude réalisée par des chercheurs des universités de Calgary et de British Columbia.

Pour le démontrer, les chercheurs ont effectué des expériences en supposant dès le départ que les traitements en mémoire préalables avant écriture sur le disque garantissent des temps d'exécution plus courts que les écritures répétées sur le disque.

L'idée est d'écrire un programme qui génère des données et de les enregistrer dans un fichier. Leur exemple consistait à créer une chaîne de caractères de 1Mo et de l'écrire sur le disque. Ils procèdent donc de deux manières différentes. La première consiste à effectuer au préalable les calculs en mémoire avant d'inscrire le résultat en une seule écriture sur le disque. La chaîne de 1Mo sera obtenue en concaténant des chaînes de tailles fixes. Par exemple, on peut obtenir la chaîne de 1Mo en concaténant 1.000.000 chaînes de 1 octet, ou en concaténant 100.000 chaînes de 10 octets, ou encore en concaténant 1.000 chaînes de 1000 octets. Les concaténations sont faites en mémoire et le résultat est inscrit sur le disque.

La deuxième méthode consiste à écrire les chaînes directement sur le disque, ce qui implique des accès répétés sans calculs en mémoire au préalable. Les données sont donc générées en petits morceaux et enregistrées immédiatement sur le disque.

Les programmes sont écrits en Java et Python et testés sous Windows et Linux. Dans chaque cas, ils ont mesuré le temps qu'il faut pour terminer l'opération pour pouvoir faire des comparaisons entre les deux méthodes.

Dans le cas in-memory, le temps d'exécution est la somme du temps mis pour effectuer les calculs en mémoire et du temps mis pour enregistrer le résultat sur le disque. Dans l'approche d'écritures répétées sur le disque, il n'y a pas d'opération en mémoire, donc on mesure uniquement le temps nécessaire pour effectuer les écritures sur disque.

Les résultats montrent que pour le programme en Java, sous Windows comme sous Linux, les accès répétés au disque donnent de meilleurs temps d'exécution pour les différentes tailles de chaînes fixées. Les résultats sont donnés dans les tableaux ci-dessous:



Pour le programme en Python, les expériences sous Linux montrent que lorsque le nombre de concaténations augmente, les opérations en mémoire semblent donner de meilleurs temps d'exécution. Mais sous Windows, les accès répétés au disque sont plus efficaces que les opérations en mémoire. Les résultats sont donnés dans les tableaux ci-dessous:



« Bien que dans de nombreux cas, les opérations en mémoire sont plus rapides qu'un algorithme équivalent qui accède au disque, les contre-exemples présentés, basés sur la vie réelle montrent que ce n'est pas toujours le cas ,» ont écrit les chercheurs.

Ces derniers notent aussi que de petits changements de code peuvent avoir des effets dramatiques sur les temps d'exécution. Ils sont en effet parvenus à exécuter la version in-memory du code Python plus vite sous Linux en changeant l'ordre de concaténation des chaines. Ce changement de code sous Windows n'a cependant pas amélioré le temps d'exécution. Ils concluent alors que ce qu'il faut pour améliorer la vitesse de travail en mémoire, c'est de savoir comment le système d'exploitation et le langage gèrent les opérations. Ils suggèrent donc qu'une connaissance du système et de meilleures pratiques de programmation sont nécessaires pour assurer que les opérations en mémoire puissent atteindre leur plein potentiel.

Source : When In-Memory Computing is Slower than Heavy Disk Usage (pdf)

Et vous ?

Que pensez-vous des résultats de cette recherche ?

S'accordent-ils avec la réalité ?

Mise à jour

Après avoir analysé l'étude, certaines personnes ont reporté qu'il y aurait des erreurs dans le code Java. Ce qui pourrait fondamentalement biaiser les résultats, du moins, pour le langage Java.

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de Médinoc
Expert éminent sénior https://www.developpez.com
Le 27/03/2015 à 13:39
Cette étude a été démontée sur le forum du Daily WTF quand un lecteur s'est rendu compte que la version "mémoire" de la concaténation de chaîne utilisait la concaténation Java, qui ré-alloue une nouvelle String à chaque fois, copiant le contenu de la précédente et ajoutant le nouveau caractère. En gros, d'après ledit lecteur tout le test s'effondre sur une erreur de String contre StringBuilder.
10  0 
Avatar de De Bain
Nouveau membre du Club https://www.developpez.com
Le 27/03/2015 à 15:48
Une petite recherche sur Google Scholar et on voit que l'article n'a jamais été publié dans une revue ou une conférence scientifique (et donc jamais été relu par d'autres scientifiques). Il s'agit seulement d'une soumission à une plateforme d'hébergement et de référencement d'articles.

C'est n'est pas "une étude scientifique", c'est "un papier écrit par 3 scientifiques qui personne n'a relu", ca n'a pas plus de valeur scientifique qu'un billet de blog.
8  0 
Avatar de bigjeff
Nouveau membre du Club https://www.developpez.com
Le 27/03/2015 à 14:47
Bonjour,

En lisant l'article j'ai été surpris des résultats, j'ai repris le code qui est dans lien. J'ai été surpris de voir que la concaténation (en Java) se faisait avec la classe String et + pour concaténer.

J'ai repris le code, j'ai crée 2 classes une pour le test en mémoire puis écriture et l'autre pour l'écriture directe afin que le deuxième cas ne récupère pas les gains d'optimisation de la JVM du premier cas.

À la place de la classe String j'ai testé StringBuilder puis StringBuffer et les résultats sont tout autre, j'ai réduit la zone mémoire à 100 000 au lieu de 1 000 000 afin que cela s'exécute plus vite sur mon PC. Voici les résultats :

La concaténation en mémoire :
Avec String et +: 2.7836 secondes
Avec StringBuffer et append : 0.00279 seconde
Avec StringBuilder et append : 0.0016 seconde

Les résultats d'écriture disque sont à peu près égaux à l'étude initiale.

Côté Python je n'ai pas assez de connaissance mais en Java il vaut mieux travailler en mémoire.

Peut-être que je me trompe.
6  0 
Avatar de Voïvode
Membre émérite https://www.developpez.com
Le 27/03/2015 à 14:06
Je viens de regarder le code. Il y a aussi un autre problème avec cette étude (en Java du moins) : les opérations prétendument disk-only sont faites avec des BufferedWriter.

Dans ce cas, c’est justement grâce à la mémoire que les écritures fréquentes sur le disque ont l’air si rapides.

[EDIT] Après vérification, Python utilise par défaut le système de cache de l’OS avec la commande open. Même topo donc.
5  0 
Avatar de iolco51
Membre habitué https://www.developpez.com
Le 27/03/2015 à 16:41
Dans la plupart des tests Windows est deux fois plus lent que Linux, lorsque ce n'est pas le cas les performances sont à peu près équivalentes. C'est intéressant.

Sinon je ne comprends pas l'objectif du test, s'il s'agit d'écrire en séquentiel sur le disque alors c'est certain, pourquoi d'abord faire une concaténation en mémoire (ce qui signifie reallocations multiples) en concaténant des chaines alors qu'on peut écrire directement sur le disque, ce qu'il faudra faire de toutes façons. C'est une évidence.

Le test intelligent est d'utiliser des memory mapped files, d'y écrire en séquentiel et de laisser l'OS travailler. Utiliser les types strings du langage de programmation pour les concaténer ensemble au préalable n'a absolument aucun sens lorsque c'est une séquence à écrire telle quelle.

Cela se résume à : Est-ce que faire les operations 1,000,000 de fois en memoire et écrire sur disque est plus rapide que ne traiter qu'une seule fois et directement écrire sur disque.... La réponse est évidente.
5  0 
Avatar de kolodz
Modérateur https://www.developpez.com
Le 27/03/2015 à 17:40
Après, quand tu lis le papier il y a des trucs qui choc :
Citation Envoyé par Publication
Kamran Karimi1, Diwakar Krishnamurthy2, Parissa Mirjafari3
Dept of Biological Sciences 1,
Dept of Electrical and Computer Engineering 2,
Dept of Chemical and Biological Engineering 3

En résumé, 2 chimistes et un électronicien...

Lecture au survole de la publication :
Citation Envoyé par Publication
Appendix 1. However, using a mutable data type such as StringBuilder or StringBuffer dramatically improved the results.

Genre information secondaire ! Pas comme si l'ensemble de papier est basé dessus...

@bigjeff : Non non tu ne te trompe pas !
Voici mon test avec le code correspondant !

String + String (Version original)
Pas les temps beaucoup trop long sur ma machine !!!
Code java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// By Kamran Karimi (kkarimi@ucalgary.ca) 
// Complete version. Runs each experiment 10 times 
import java.io.IOException; 
import java.io.FileWriter; 
import java.io.BufferedWriter; 
  
public class Test { 
	public static void main(String[] args) { 
		// number of increments for the file content. 
		int numAdd = 1; // Additional changes are needed when NUM_ADD = 1e6 
		int NUM_ITERATIONS = 10; 
		long totalMemory = (long) 1000000; // total amount of memory in bytes 
		String addString = ""; 
		for (int i = 0; i < numAdd; i++) { 
			addString += "1"; 
		} 
		double[] stringTimes = new double[NUM_ITERATIONS]; 
		double[] fileTimes1 = new double[NUM_ITERATIONS]; 
		double[] fileTimes2 = new double[NUM_ITERATIONS]; 
		for (int count = 0; count < NUM_ITERATIONS; count++) { 
			stringTimes[count] = fileTimes1[count] = fileTimes2[count] = 0; 
			BufferedWriter writer; 
			// First part: in-memory 
			long numIter = totalMemory / addString.length(); 
			String  concatString = ""; 
			long startTime = System.currentTimeMillis(); 
			for (int i = 0; i < numIter; i++) { 
				concatString+=addString; 
			} 
			long endTime = System.currentTimeMillis(); 
			double stringTime = (endTime - startTime) / 1000.0; 
			stringTimes[count] = stringTime; 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				writer.write(concatString); 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
			} catch (IOException e) { 
			} 
			double fileTime = (endTime - startTime) / 1000.0; 
			fileTimes1[count] = fileTime; 
			// Second part: disk-only 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				for (int i = 0; i < numIter; i++) { 
					writer.write(addString); 
				} 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
				fileTime = (endTime - startTime) / 1000.0; 
				fileTimes2[count] = fileTime; 
			} catch (IOException e) { 
			} 
		} 
		double stringMean = 0; 
		for (int i = 0; i < stringTimes.length; i++) { 
			stringMean += stringTimes[i]; 
		} 
		stringMean /= stringTimes.length; 
		double fileMean1 = 0; 
		for (int i = 0; i < fileTimes1.length; i++) { 
			fileMean1 += fileTimes1[i]; 
		} 
		fileMean1 /= fileTimes1.length; 
		double fileMean2 = 0; 
		for (int i = 0; i < fileTimes2.length; i++) { 
			fileMean2 += fileTimes2[i]; 
		} 
		fileMean2 /= fileTimes2.length; 
		System.err.println("In-memory mean: string time" + stringMean); 
		System.err.println("In-memory mean: file time " + fileMean1); 
		System.err.println("In-memory mean: Total " + (fileMean1+stringMean)); 
		System.err.println("Disk-only mean: file time " + fileMean2); 
	} 
}

StringBuilder append
In-memory mean: string time0.020599999999999997
In-memory mean: file time 0.007000000000000001
In-memory mean: Total 0.0276
Disk-only mean: file time 0.0397

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// By Kamran Karimi (kkarimi@ucalgary.ca) 
// Complete version. Runs each experiment 10 times 
import java.io.IOException; 
import java.io.FileWriter; 
import java.io.BufferedWriter; 
  
public class Test { 
	public static void main(String[] args) { 
		// number of increments for the file content. 
		int numAdd = 1; // Additional changes are needed when NUM_ADD = 1e6 
		int NUM_ITERATIONS = 10; 
		long totalMemory = (long) 1000000; // total amount of memory in bytes 
		String addString = ""; 
		for (int i = 0; i < numAdd; i++) { 
			addString += "1"; 
		} 
		double[] stringTimes = new double[NUM_ITERATIONS]; 
		double[] fileTimes1 = new double[NUM_ITERATIONS]; 
		double[] fileTimes2 = new double[NUM_ITERATIONS]; 
		for (int count = 0; count < NUM_ITERATIONS; count++) { 
			stringTimes[count] = fileTimes1[count] = fileTimes2[count] = 0; 
			BufferedWriter writer; 
			// First part: in-memory 
			long numIter = totalMemory / addString.length(); 
			StringBuilder  concatString = new StringBuilder (); 
			long startTime = System.currentTimeMillis(); 
			for (int i = 0; i < numIter; i++) { 
				concatString.append(addString); 
			} 
			long endTime = System.currentTimeMillis(); 
			double stringTime = (endTime - startTime) / 1000.0; 
			stringTimes[count] = stringTime; 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				writer.write(concatString.toString()); 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
			} catch (IOException e) { 
			} 
			double fileTime = (endTime - startTime) / 1000.0; 
			fileTimes1[count] = fileTime; 
			// Second part: disk-only 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				for (int i = 0; i < numIter; i++) { 
					writer.write(addString); 
				} 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
				fileTime = (endTime - startTime) / 1000.0; 
				fileTimes2[count] = fileTime; 
			} catch (IOException e) { 
			} 
		} 
		double stringMean = 0; 
		for (int i = 0; i < stringTimes.length; i++) { 
			stringMean += stringTimes[i]; 
		} 
		stringMean /= stringTimes.length; 
		double fileMean1 = 0; 
		for (int i = 0; i < fileTimes1.length; i++) { 
			fileMean1 += fileTimes1[i]; 
		} 
		fileMean1 /= fileTimes1.length; 
		double fileMean2 = 0; 
		for (int i = 0; i < fileTimes2.length; i++) { 
			fileMean2 += fileTimes2[i]; 
		} 
		fileMean2 /= fileTimes2.length; 
		System.err.println("In-memory mean: string time" + stringMean); 
		System.err.println("In-memory mean: file time " + fileMean1); 
		System.err.println("In-memory mean: Total " + (fileMean1+stringMean)); 
		System.err.println("Disk-only mean: file time " + fileMean2); 
	} 
}

StringBuffer append
In-memory mean: string time0.037399999999999996
In-memory mean: file time 0.007000000000000001
In-memory mean: Total 0.044399999999999995
Disk-only mean: file time 0.041499999999999995

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// By Kamran Karimi (kkarimi@ucalgary.ca) 
// Complete version. Runs each experiment 10 times 
import java.io.IOException; 
import java.io.FileWriter; 
import java.io.BufferedWriter; 
  
public class Test { 
	public static void main(String[] args) { 
		// number of increments for the file content. 
		int numAdd = 1; // Additional changes are needed when NUM_ADD = 1e6 
		int NUM_ITERATIONS = 10; 
		long totalMemory = (long) 1000000; // total amount of memory in bytes 
		String addString = ""; 
		for (int i = 0; i < numAdd; i++) { 
			addString += "1"; 
		} 
		double[] stringTimes = new double[NUM_ITERATIONS]; 
		double[] fileTimes1 = new double[NUM_ITERATIONS]; 
		double[] fileTimes2 = new double[NUM_ITERATIONS]; 
		for (int count = 0; count < NUM_ITERATIONS; count++) { 
			stringTimes[count] = fileTimes1[count] = fileTimes2[count] = 0; 
			BufferedWriter writer; 
			// First part: in-memory 
			long numIter = totalMemory / addString.length(); 
			StringBuffer concatString = new StringBuffer (); 
			long startTime = System.currentTimeMillis(); 
			for (int i = 0; i < numIter; i++) { 
				concatString.append(addString); 
			} 
			long endTime = System.currentTimeMillis(); 
			double stringTime = (endTime - startTime) / 1000.0; 
			stringTimes[count] = stringTime; 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				writer.write(concatString.toString()); 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
			} catch (IOException e) { 
			} 
			double fileTime = (endTime - startTime) / 1000.0; 
			fileTimes1[count] = fileTime; 
			// Second part: disk-only 
			try { 
				writer = new BufferedWriter(new FileWriter("test.txt")); 
				startTime = System.currentTimeMillis(); 
				for (int i = 0; i < numIter; i++) { 
					writer.write(addString); 
				} 
				writer.flush(); 
				writer.close(); 
				endTime = System.currentTimeMillis(); 
				fileTime = (endTime - startTime) / 1000.0; 
				fileTimes2[count] = fileTime; 
			} catch (IOException e) { 
			} 
		} 
		double stringMean = 0; 
		for (int i = 0; i < stringTimes.length; i++) { 
			stringMean += stringTimes[i]; 
		} 
		stringMean /= stringTimes.length; 
		double fileMean1 = 0; 
		for (int i = 0; i < fileTimes1.length; i++) { 
			fileMean1 += fileTimes1[i]; 
		} 
		fileMean1 /= fileTimes1.length; 
		double fileMean2 = 0; 
		for (int i = 0; i < fileTimes2.length; i++) { 
			fileMean2 += fileTimes2[i]; 
		} 
		fileMean2 /= fileTimes2.length; 
		System.err.println("In-memory mean: string time" + stringMean); 
		System.err.println("In-memory mean: file time " + fileMean1); 
		System.err.println("In-memory mean: Total " + (fileMean1+stringMean)); 
		System.err.println("Disk-only mean: file time " + fileMean2); 
	} 
}

Winner is :
StringBuilder append avec 0.0276

Cordialement,
Patrick Kolodziejczyk.
5  0 
Avatar de JackJnr
Membre confirmé https://www.developpez.com
Le 27/03/2015 à 14:22
Du coup si je comprends bien, ces chercheurs sont de futurs chercheurs... d'emploi !

--> [] !
4  0 
Avatar de pcaboche
Rédacteur https://www.developpez.com
Le 28/03/2015 à 2:06
Citation Envoyé par Squisqui Voir le message
Sans même parler d'implémentation, le test est tellement peu représentatif de la réalité qu'on se dit qu'on est bien Vendredi
Vu la méthodologie utilisée et les conclusions de l'étude, j'ai plutôt cru à un poisson d'Avril.
2  0 
Avatar de Traroth2
Membre chevronné https://www.developpez.com
Le 30/03/2015 à 11:15
En gros, leur étude, ça veut dire : "quand la seule opération qu'on veut faire dans le code, c'est écrire sur le disque, ça va plus vite d'écrire directement sur le disque sans faire d'opération en mémoire". C'est assez évident, non ?
2  0 
Avatar de Voïvode
Membre émérite https://www.developpez.com
Le 27/03/2015 à 14:37
@Washmid
C’est vrai uniquement avec les concaténations simples. Le compilateur sait optimiser "a" + "b" + "c" sans problème.

Dans une boucle, en revanche, un StringBuilder est plus que recommandé. C’est exactement ce qu’ils n’ont pas fait dans leurs tests.
1  0