web-dev-qa-db-fra.com

SQLite Performance Benchmark - pourquoi: la mémoire: si lente ... seulement 1,5 fois plus rapide que le disque?

Pourquoi la mémoire est-elle si lente dans sqlite?

J'ai essayé de voir s'il y avait des améliorations de performances obtenues en utilisant sqlite en mémoire vs sqlite sur disque. Fondamentalement, j'aimerais échanger le temps de démarrage et la mémoire pour obtenir des requêtes extrêmement rapides qui font pas frapper le disque au cours de l'application.

Cependant, le benchmark suivant ne me donne qu'un facteur de 1,5X en vitesse améliorée. Ici, je génère 1M lignes de données aléatoires et les charge à la fois dans une version basée sur disque et en mémoire de la même table. J'ai ensuite exécuté des requêtes aléatoires sur les deux dbs, en retournant des ensembles de taille environ 300k. Je m'attendais à ce que la version basée sur la mémoire soit considérablement plus rapide, mais comme mentionné, je n'obtiens que des accélérations 1.5X.

J'ai expérimenté plusieurs autres tailles de dbs et d'ensembles de requêtes; l'avantage de: memory: does semble augmenter à mesure que le nombre de lignes de la base de données augmente. Je ne sais pas pourquoi l'avantage est si petit, même si j'avais quelques hypothèses:

  • la table utilisée n'est pas assez grande (en rangées) pour faire: mémoire: un énorme gagnant
  • plus de jointures/tables rendraient la: mémoire: avantage plus apparent
  • il y a une sorte de mise en cache au niveau de la connexion ou du système d'exploitation de sorte que les résultats précédents sont accessibles d'une manière ou d'une autre, corrompant le test de performance
  • il y a une sorte d'accès au disque caché que je ne vois pas (je n'ai pas encore essayé lsof, mais j'ai désactivé les PRAGMA pour la journalisation)

Est-ce que je fais quelque chose de mal ici? Une réflexion sur pourquoi: la mémoire: ne produit-elle pas de recherches presque instantanées? Voici la référence:

==> sqlite_memory_vs_disk_benchmark.py <==

#!/usr/bin/env python
"""Attempt to see whether :memory: offers significant performance benefits.

"""
import os
import time
import sqlite3
import numpy as np

def load_mat(conn,mat):
    c = conn.cursor()

    #Try to avoid hitting disk, trading safety for speed.
    #http://stackoverflow.com/questions/304393
    c.execute('PRAGMA temp_store=MEMORY;')
    c.execute('PRAGMA journal_mode=MEMORY;')

    # Make a demo table
    c.execute('create table if not exists demo (id1 int, id2 int, val real);')
    c.execute('create index id1_index on demo (id1);')
    c.execute('create index id2_index on demo (id2);')
    for row in mat:
        c.execute('insert into demo values(?,?,?);', (row[0],row[1],row[2]))
    conn.commit()

def querytime(conn,query):
    start = time.time()
    foo = conn.execute(query).fetchall()
    diff = time.time() - start
    return diff

#1) Build some fake data with 3 columns: int, int, float
nn   = 1000000 #numrows
cmax = 700    #num uniques in 1st col
gmax = 5000   #num uniques in 2nd col

mat = np.zeros((nn,3),dtype='object')
mat[:,0] = np.random.randint(0,cmax,nn)
mat[:,1] = np.random.randint(0,gmax,nn)
mat[:,2] = np.random.uniform(0,1,nn)

#2) Load it into both dbs & build indices
try: os.unlink('foo.sqlite')
except OSError: pass

conn_mem = sqlite3.connect(":memory:")
conn_disk = sqlite3.connect('foo.sqlite')
load_mat(conn_mem,mat)
load_mat(conn_disk,mat)
del mat

#3) Execute a series of random queries and see how long it takes each of these
numqs = 10
numqrows = 300000 #max number of ids of each kind
results = np.zeros((numqs,3))
for qq in range(numqs):
    qsize = np.random.randint(1,numqrows,1)
    id1a = np.sort(np.random.permutation(np.arange(cmax))[0:qsize]) #ensure uniqueness of ids queried
    id2a = np.sort(np.random.permutation(np.arange(gmax))[0:qsize])
    id1s = ','.join([str(xx) for xx in id1a])
    id2s = ','.join([str(xx) for xx in id2a])
    query = 'select * from demo where id1 in (%s) AND id2 in (%s);' % (id1s,id2s)

    results[qq,0] = round(querytime(conn_disk,query),4)
    results[qq,1] = round(querytime(conn_mem,query),4)
    results[qq,2] = int(qsize)

#4) Now look at the results
print "  disk | memory | qsize"
print "-----------------------"
for row in results:
    print "%.4f | %.4f | %d" % (row[0],row[1],row[2])

Voici les résultats. Notez que le disque prend environ 1,5 fois plus de temps que la mémoire pour un éventail assez large de tailles de requête.

[ramanujan:~]$python -OO sqlite_memory_vs_disk_clean.py
  disk | memory | qsize
-----------------------
9.0332 | 6.8100 | 12630
9.0905 | 6.6953 | 5894
9.0078 | 6.8384 | 17798
9.1179 | 6.7673 | 60850
9.0629 | 6.8355 | 94854
8.9688 | 6.8093 | 17940
9.0785 | 6.6993 | 58003
9.0309 | 6.8257 | 85663
9.1423 | 6.7411 | 66047
9.1814 | 6.9794 | 11345

Ne devrait pas RAM être presque instantané par rapport au disque? Qu'est-ce qui ne va pas ici?

Éditer

Quelques bonnes suggestions ici.

Je suppose que le principal point de départ pour moi est que ** il n'y a probablement aucun moyen de faire: mémoire: absolument plus rapide, mais il existe un moyen de rendre l'accès au disque relativement plus lent. **

En d'autres termes, la référence mesure correctement les performances réalistes de la mémoire, mais pas les performances réalistes du disque (par exemple parce que le pragma cache_size est trop grand ou parce que je ne fais pas d'écritures). Je vais jouer avec ces paramètres et publier mes résultats lorsque j'en aurai l'occasion.

Cela dit, s'il y a quelqu'un qui pense que je peux extraire un peu plus de vitesse de la base de données en mémoire (autrement qu'en augmentant le cache_size et le default_cache_size, ce que je ferai), je suis tout à fait à l'écoute ...

47
ramanujan

Cela a à voir avec le fait que SQLite a un cache de pages. Selon le Documentation , le cache de page par défaut est de 2000 pages 1K ou environ 2 Mo. Comme il s'agit d'environ 75% à 90% de vos données, il n'est pas surprenant que les deux nombres soient très similaires. Je suppose qu'en plus du cache de page SQLite, le reste des données est toujours dans le cache de disque du système d'exploitation. Si vous obtenez SQLite pour vider le cache de page (et le cache de disque), vous verrez des différences vraiment importantes.

39
Thomas Jones-Low

Ma question est la suivante: qu'essayez-vous de comparer?

Comme déjà mentionné, SQLite's: memory: DB est exactement la même que celle basée sur le disque, c'est-à-dire paginée, et la seule différence est que les pages ne sont jamais écrites sur le disque. Donc, la seule différence entre les deux est l'écriture sur le disque: mémoire: n'a pas besoin de faire (il n'a pas non plus besoin de faire de lecture de disque non plus, lorsqu'une page de disque doit être déchargée du cache).

Mais la lecture/écriture à partir du cache peut ne représenter qu'une fraction du temps de traitement des requêtes, selon la requête. Votre requête a une clause where avec deux grands ensembles d'identifiants dont les lignes sélectionnées doivent être membres, ce qui est coûteux.

Comme Cary Millsap le démontre dans son blog sur l'optimisation d'Oracle (voici un article représentatif: http://carymillsap.blogspot.com/2009/06/profiling-with-my-boy.html ), vous avez besoin pour comprendre quelles parties du traitement des requêtes prennent du temps. En supposant que les tests d'appartenance définis représentaient 90% du temps de requête, et le disque IO 10%, aller à: memory: enregistre uniquement ces 10%. C'est un exemple extrême peu susceptible d'être représentatif , mais j'espère que cela illustre que votre requête particulière incline les résultats. Utilisez une requête plus simple et les parties IO du traitement de la requête augmenteront, et donc l'avantage de: memory :.

En guise de note finale, nous avons expérimenté avec les tables virtuelles de SQLite, où vous êtes en charge du stockage réel, et en utilisant des conteneurs C++, qui sont typés contrairement à la manière de SQLite de stocker les valeurs des cellules, nous avons pu voir une amélioration significative du temps de traitement over: memory :, mais ça devient un peu sujet;) --DD

PS: Je n'ai pas assez de Karma pour commenter le post le plus populaire de ce fil, donc je commente ici :) pour dire que la version récente de SQLite n'utilise pas les pages de 1 Ko par défaut sous Windows: http: //www.sqlite.org/changes.html#version_3_6_12

20
ddevienne

Vous faites des SELECT, vous utilisez un cache mémoire. Essayez d'entrelacer les SELECT avec les UPDATE.

7
vartec

La base de données mémoire dans SQLite est en fait un cache de pages qui ne touche jamais le disque. Vous devez donc oublier d'utiliser la mémoire db dans SQLite pour les réglages de performances

Il est possible de désactiver le journal, de désactiver le mode de synchronisation, de définir le cache de grandes pages et vous aurez presque les mêmes performances sur la plupart des opérations, mais la durabilité sera perdue.

D'après votre code, il est absolument clair que vous DEVRIEZ RÉUTILISER la commande et SEULEMENT LIER les paramètres, car cela a pris plus de 90% de vos performances de test.

6
Mash

Merci pour le code. J'ai testé sur 2 x XEON 2690 avec 192 Go RAM avec 4 disques durs SCSI 15k en RAID 5 et les résultats sont:

  disk | memory | qsize
-----------------------
6.3590 | 2.3280 | 15713
6.6250 | 2.3690 | 8914
6.0040 | 2.3260 | 225168
6.0210 | 2.4080 | 132388
6.1400 | 2.4050 | 264038

L'augmentation de la vitesse de la mémoire est importante.

5
jankos

Se pourrait-il que sqlite3 n'écrive pas réellement vos données sur le disque à partir du cache? ce qui pourrait expliquer pourquoi les chiffres sont similaires.

Il est également possible que votre système d'exploitation soit en train de paginer en raison d'une mémoire insuffisante.

1
Felix

Je note que vous vous concentrez sur les requêtes qui impliquent des ensembles de données relativement volumineux à renvoyer. Je me demande quel effet vous verriez avec des ensembles de données plus petits? Pour renvoyer une seule ligne plusieurs fois, le disque doit en rechercher beaucoup - le temps d'accès aléatoire à la mémoire peut être beaucoup plus rapide.

1
1800 INFORMATION

les tableaux numpy sont plus lents que dict et Tuple et d'autres séquences d'objets jusqu'à ce que vous ayez affaire à 5 millions d'objets ou plus dans une séquence. Vous pouvez améliorer considérablement la vitesse de traitement d'énormes quantités de données en les itérant et en utilisant des générateurs pour éviter de créer et de recréer de gros objets temporaires.

numpy est devenu votre facteur limitant car il est conçu pour offrir des performances linéaires. Ce n'est pas une étoile avec de petites ou même de grandes quantités de données. Mais les performances de numphy ne se transforment pas en courbe à mesure que l'ensemble de données augmente. Cela reste une ligne droite.

De plus, SQLite n'est qu'une base de données très rapide. Plus rapide que la plupart des bases de données de serveur. Cela soulève la question de savoir pourquoi quiconque utiliserait des bases de données NOSQL alors qu'une base de données légère et ultra-rapide à tolérance de pannes qui utilise SQL existe et est mise à l'épreuve dans tout, des navigateurs aux téléphones mobiles depuis des années.

0
freegnu