4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
13 ** This file implements "key-value" performance test for SQLite. The
14 ** purpose is to compare the speed of SQLite for accessing large BLOBs
15 ** versus reading those same BLOB values out of individual files in the
18 ** Run "kvtest" with no arguments for on-line help, or see comments below.
22 ** (1) Gather this source file and a recent SQLite3 amalgamation with its
23 ** header into the working directory. You should have:
25 ** kvtest.c >--- this file
26 ** sqlite3.c \___ SQLite
27 ** sqlite3.h / amlagamation & header
29 ** (2) Run you compiler against the two C source code files.
31 ** (a) On linux or mac:
33 ** OPTS="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION"
34 ** gcc -Os -I. $OPTS kvtest.c sqlite3.c -o kvtest
36 ** The $OPTS options can be omitted. The $OPTS merely omit
37 ** the need to link against -ldl and -lpthread, or whatever
38 ** the equivalent libraries are called on your system.
40 ** (b) Windows with MSVC:
42 ** cl -I. kvtest.c sqlite3.c
46 ** (1) Create a test database by running "kvtest init" with appropriate
47 ** options. See the help message for available options.
49 ** (2) Construct the corresponding pile-of-files database on disk using
50 ** the "kvtest export" command.
52 ** (3) Run tests using "kvtest run" against either the SQLite database or
53 ** the pile-of-files database and with appropriate options.
57 ** ./kvtest init x1.db --count 100000 --size 10000
59 ** ./kvtest export x1.db x1
60 ** ./kvtest run x1.db --count 10000 --max-id 1000000
61 ** ./kvtest run x1 --count 10000 --max-id 1000000
63 static const char zHelp
[] =
64 "Usage: kvhelp COMMAND ARGS...\n"
66 " kvhelp init DBFILE --count N --size M --pagesize X\n"
68 " Generate a new test database file named DBFILE containing N\n"
69 " BLOBs each of size M bytes. The page size of the new database\n"
72 " kvhelp export DBFILE DIRECTORY\n"
74 " Export all the blobs in the kv table of DBFILE into separate\n"
75 " files in DIRECTORY.\n"
77 " kvhelp run DBFILE [options]\n"
79 " Run a performance test. DBFILE can be either the name of a\n"
80 " database or a directory containing sample files. Options:\n"
82 " --asc Read blobs in ascending order\n"
83 " --blob-api Use the BLOB API\n"
84 " --cache-size N Database cache size\n"
85 " --count N Read N blobs\n"
86 " --desc Read blobs in descending order\n"
87 " --max-id N Maximum blob key to use\n"
88 " --random Read blobs in a random order\n"
89 " --start N Start reading with this blob key\n"
92 /* Reference resources used */
95 #include <sys/types.h>
104 /* Provide Windows equivalent for the needed parts of unistd.h */
107 # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
108 # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
109 # define access _access
114 ** Show thqe help text and quit.
116 static void showHelp(void){
117 fprintf(stdout
, "%s", zHelp
);
122 ** Show an error message an quit.
124 static void fatalError(const char *zFormat
, ...){
126 fprintf(stdout
, "ERROR: ");
127 va_start(ap
, zFormat
);
128 vfprintf(stdout
, zFormat
, ap
);
130 fprintf(stdout
, "\n");
135 ** Check the filesystem object zPath. Determine what it is:
137 ** PATH_DIR A directory
138 ** PATH_DB An SQLite database
139 ** PATH_NEXIST Does not exist
140 ** PATH_OTHER Something else
144 #define PATH_NEXIST 0
145 #define PATH_OTHER 99
146 static int pathType(const char *zPath
){
149 if( access(zPath
,R_OK
) ) return PATH_NEXIST
;
150 memset(&x
, 0, sizeof(x
));
151 rc
= stat(zPath
, &x
);
152 if( rc
<0 ) return PATH_OTHER
;
153 if( S_ISDIR(x
.st_mode
) ) return PATH_DIR
;
154 if( (x
.st_size
%512)==0 ) return PATH_DB
;
159 ** Return the size of a file in bytes. Or return -1 if the
160 ** named object is not a regular file or does not exist.
162 static sqlite3_int64
fileSize(const char *zPath
){
165 memset(&x
, 0, sizeof(x
));
166 rc
= stat(zPath
, &x
);
167 if( rc
<0 ) return -1;
168 if( !S_ISREG(x
.st_mode
) ) return -1;
173 ** A Pseudo-random number generator with a fixed seed. Use this so
174 ** that the same sequence of "random" numbers are generated on each
175 ** run, for repeatability.
177 static unsigned int randInt(void){
178 static unsigned int x
= 0x333a13cd;
179 static unsigned int y
= 0xecb2adea;
180 x
= (x
>>1) ^ ((1+~(x
&1)) & 0xd0000001);
181 y
= y
*1103515245 + 12345;
186 ** Do database initialization.
188 static int initMain(int argc
, char **argv
){
198 assert( strcmp(argv
[1],"init")==0 );
201 for(i
=3; i
<argc
; i
++){
203 if( z
[0]!='-' ) fatalError("unknown argument: \"%s\"", z
);
205 if( strcmp(z
, "-count")==0 ){
206 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
207 nCount
= atoi(argv
[++i
]);
208 if( nCount
<1 ) fatalError("the --count must be positive");
211 if( strcmp(z
, "-size")==0 ){
212 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
213 sz
= atoi(argv
[++i
]);
214 if( sz
<1 ) fatalError("the --size must be positive");
217 if( strcmp(z
, "-pagesize")==0 ){
218 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
219 pgsz
= atoi(argv
[++i
]);
220 if( pgsz
<512 || pgsz
>65536 || ((pgsz
-1)&pgsz
)!=0 ){
221 fatalError("the --pagesize must be power of 2 between 512 and 65536");
225 fatalError("unknown option: \"%s\"", argv
[i
]);
227 rc
= sqlite3_open(zDb
, &db
);
229 fatalError("cannot open database \"%s\": %s", zDb
, sqlite3_errmsg(db
));
231 zSql
= sqlite3_mprintf(
232 "DROP TABLE IF EXISTS kv;\n"
233 "PRAGMA page_size=%d;\n"
236 "CREATE TABLE kv(k INTEGER PRIMARY KEY, v BLOB);\n"
237 "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<%d)"
238 " INSERT INTO kv(k,v) SELECT x, randomblob(%d) FROM c;\n"
242 rc
= sqlite3_exec(db
, zSql
, 0, 0, &zErrMsg
);
243 if( rc
) fatalError("database create failed: %s", zErrMsg
);
250 ** Implementation of the "writefile(X,Y)" SQL function. The argument Y
251 ** is written into file X. The number of bytes written is returned. Or
252 ** NULL is returned if something goes wrong, such as being unable to open
253 ** file X for writing.
255 static void writefileFunc(
256 sqlite3_context
*context
,
265 zFile
= (const char*)sqlite3_value_text(argv
[0]);
266 if( zFile
==0 ) return;
267 out
= fopen(zFile
, "wb");
269 z
= (const char*)sqlite3_value_blob(argv
[1]);
273 rc
= fwrite(z
, 1, sqlite3_value_bytes(argv
[1]), out
);
276 printf("\r%s ", zFile
); fflush(stdout
);
277 sqlite3_result_int64(context
, rc
);
281 ** Export the kv table to individual files in the filesystem
283 static int exportMain(int argc
, char **argv
){
291 assert( strcmp(argv
[1],"export")==0 );
294 if( argc
!=4 ) fatalError("Usage: kvtest export DATABASE DIRECTORY");
296 if( pathType(zDir
)!=PATH_DIR
){
297 fatalError("object \"%s\" is not a directory", zDir
);
299 rc
= sqlite3_open(zDb
, &db
);
301 fatalError("cannot open database \"%s\": %s", zDb
, sqlite3_errmsg(db
));
303 sqlite3_create_function(db
, "writefile", 2, SQLITE_UTF8
, 0,
304 writefileFunc
, 0, 0);
305 zSql
= sqlite3_mprintf(
306 "SELECT writefile(printf('%s/%%06d',k),v) FROM kv;",
309 rc
= sqlite3_exec(db
, zSql
, 0, 0, &zErrMsg
);
310 if( rc
) fatalError("database create failed: %s", zErrMsg
);
318 ** Read the content of file zName into memory obtained from sqlite3_malloc64()
319 ** and return a pointer to the buffer. The caller is responsible for freeing
322 ** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
325 ** For convenience, a nul-terminator byte is always appended to the data read
326 ** from the file before the buffer is returned. This byte is not included in
327 ** the final value of (*pnByte), if applicable.
329 ** NULL is returned if any error is encountered. The final value of *pnByte
330 ** is undefined in this case.
332 static unsigned char *readFile(const char *zName
, int *pnByte
){
333 FILE *in
; /* FILE from which to read content of zName */
334 sqlite3_int64 nIn
; /* Size of zName in bytes */
335 size_t nRead
; /* Number of bytes actually read */
336 unsigned char *pBuf
; /* Content read from disk */
338 nIn
= fileSize(zName
);
339 if( nIn
<0 ) return 0;
340 in
= fopen(zName
, "rb");
341 if( in
==0 ) return 0;
342 pBuf
= sqlite3_malloc64( nIn
);
343 if( pBuf
==0 ) return 0;
344 nRead
= fread(pBuf
, nIn
, 1, in
);
350 if( pnByte
) *pnByte
= nIn
;
355 ** Return the current time in milliseconds since the beginning of
358 static sqlite3_int64
timeOfDay(void){
359 static sqlite3_vfs
*clockVfs
= 0;
361 if( clockVfs
==0 ) clockVfs
= sqlite3_vfs_find(0);
362 if( clockVfs
->iVersion
>=2 && clockVfs
->xCurrentTimeInt64
!=0 ){
363 clockVfs
->xCurrentTimeInt64(clockVfs
, &t
);
366 clockVfs
->xCurrentTime(clockVfs
, &r
);
367 t
= (sqlite3_int64
)(r
*86400000.0);
372 /* Blob access order */
375 #define ORDER_RANDOM 3
378 ** Run a performance test
380 static int runMain(int argc
, char **argv
){
381 int eType
; /* Is zDb a database or a directory? */
382 char *zDb
; /* Database or directory name */
383 int i
; /* Loop counter */
384 int rc
; /* Return code from SQLite calls */
385 int nCount
= 1000; /* Number of blob fetch operations */
386 int nExtra
= 0; /* Extra cycles */
387 int iKey
= 1; /* Next blob key */
388 int iMax
= 1000; /* Largest allowed key */
389 int iPagesize
= 0; /* Database page size */
390 int iCache
= 1000; /* Database cache size in kibibytes */
391 int bBlobApi
= 0; /* Use the incremental blob I/O API */
392 int eOrder
= ORDER_ASC
; /* Access order */
393 sqlite3
*db
= 0; /* Database connection */
394 sqlite3_stmt
*pStmt
= 0; /* Prepared statement for SQL access */
395 sqlite3_blob
*pBlob
= 0; /* Handle for incremental Blob I/O */
396 sqlite3_int64 tmStart
; /* Start time */
397 sqlite3_int64 tmElapsed
; /* Elapsed time */
398 int nData
= 0; /* Bytes of data */
399 sqlite3_int64 nTotal
= 0; /* Total data read */
400 unsigned char *pData
; /* Content of the blob */
403 assert( strcmp(argv
[1],"run")==0 );
406 eType
= pathType(zDb
);
407 if( eType
==PATH_OTHER
) fatalError("unknown object type: \"%s\"", zDb
);
408 if( eType
==PATH_NEXIST
) fatalError("object does not exist: \"%s\"", zDb
);
409 for(i
=3; i
<argc
; i
++){
411 if( z
[0]!='-' ) fatalError("unknown argument: \"%s\"", z
);
413 if( strcmp(z
, "-count")==0 ){
414 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
415 nCount
= atoi(argv
[++i
]);
416 if( nCount
<1 ) fatalError("the --count must be positive");
419 if( strcmp(z
, "-max-id")==0 ){
420 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
421 iMax
= atoi(argv
[++i
]);
422 if( iMax
<1 ) fatalError("the --max-id must be positive");
425 if( strcmp(z
, "-start")==0 ){
426 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
427 iKey
= atoi(argv
[++i
]);
428 if( iKey
<1 ) fatalError("the --start must be positive");
431 if( strcmp(z
, "-cache-size")==0 ){
432 if( i
==argc
-1 ) fatalError("missing argument on \"%s\"", argv
[i
]);
433 iCache
= atoi(argv
[++i
]);
436 if( strcmp(z
, "-random")==0 ){
437 eOrder
= ORDER_RANDOM
;
440 if( strcmp(z
, "-asc")==0 ){
444 if( strcmp(z
, "-desc")==0 ){
448 if( strcmp(z
, "-blob-api")==0 ){
452 fatalError("unknown option: \"%s\"", argv
[i
]);
454 tmStart
= timeOfDay();
455 if( eType
==PATH_DB
){
457 rc
= sqlite3_open(zDb
, &db
);
459 fatalError("cannot open database \"%s\": %s", zDb
, sqlite3_errmsg(db
));
461 zSql
= sqlite3_mprintf("PRAGMA cache_size=%d", iCache
);
462 sqlite3_exec(db
, zSql
, 0, 0, 0);
465 sqlite3_prepare_v2(db
, "PRAGMA page_size", -1, &pStmt
, 0);
466 if( sqlite3_step(pStmt
)==SQLITE_ROW
){
467 iPagesize
= sqlite3_column_int(pStmt
, 0);
469 sqlite3_finalize(pStmt
);
470 sqlite3_prepare_v2(db
, "PRAGMA cache_size", -1, &pStmt
, 0);
471 if( sqlite3_step(pStmt
)==SQLITE_ROW
){
472 iCache
= sqlite3_column_int(pStmt
, 0);
476 sqlite3_finalize(pStmt
);
478 sqlite3_exec(db
, "BEGIN", 0, 0, 0);
480 for(i
=0; i
<nCount
; i
++){
481 if( eType
==PATH_DIR
){
482 /* CASE 1: Reading blobs out of separate files */
484 zKey
= sqlite3_mprintf("%s/%06d", zDb
, iKey
);
486 pData
= readFile(zKey
, &nData
);
489 }else if( bBlobApi
){
490 /* CASE 2: Reading from database using the incremental BLOB I/O API */
492 rc
= sqlite3_blob_open(db
, "main", "kv", "v", iKey
, 0, &pBlob
);
494 fatalError("could not open sqlite3_blob handle: %s",
498 rc
= sqlite3_blob_reopen(pBlob
, iKey
);
501 nData
= sqlite3_blob_bytes(pBlob
);
502 pData
= sqlite3_malloc( nData
+1 );
503 if( pData
==0 ) fatalError("cannot allocate %d bytes", nData
+1);
504 rc
= sqlite3_blob_read(pBlob
, pData
, nData
, 0);
506 fatalError("could not read the blob at %d: %s", iKey
,
512 /* CASE 3: Reading from database using SQL */
514 rc
= sqlite3_prepare_v2(db
,
515 "SELECT v FROM kv WHERE k=?1", -1, &pStmt
, 0);
517 fatalError("cannot prepare query: %s", sqlite3_errmsg(db
));
520 sqlite3_reset(pStmt
);
522 sqlite3_bind_int(pStmt
, 1, iKey
);
523 rc
= sqlite3_step(pStmt
);
524 if( rc
==SQLITE_ROW
){
525 nData
= sqlite3_column_bytes(pStmt
, 0);
526 pData
= (unsigned char*)sqlite3_column_blob(pStmt
, 0);
531 if( eOrder
==ORDER_ASC
){
533 if( iKey
>iMax
) iKey
= 1;
534 }else if( eOrder
==ORDER_DESC
){
536 if( iKey
<=0 ) iKey
= iMax
;
538 iKey
= (randInt()%iMax
)+1;
541 if( nData
==0 ){ nCount
++; nExtra
++; }
543 if( pStmt
) sqlite3_finalize(pStmt
);
544 if( pBlob
) sqlite3_blob_close(pBlob
);
545 if( db
) sqlite3_close(db
);
546 tmElapsed
= timeOfDay() - tmStart
;
548 printf("%d cycles due to %d misses\n", nCount
, nExtra
);
550 if( eType
==PATH_DB
){
551 printf("SQLite version: %s\n", sqlite3_libversion());
553 printf("--count %d --max-id %d", nCount
-nExtra
, iMax
);
554 if( eType
==PATH_DB
){
555 printf(" --cache-size %d", iCache
);
558 case ORDER_RANDOM
: printf(" --random\n"); break;
559 case ORDER_DESC
: printf(" --desc\n"); break;
560 default: printf(" --asc\n"); break;
562 if( iPagesize
) printf("Database page size: %d\n", iPagesize
);
563 printf("Total elapsed time: %.3f\n", tmElapsed
/1000.0);
564 printf("Microseconds per BLOB read: %.3f\n", tmElapsed
*1000.0/nCount
);
565 printf("Content read rate: %.1f MB/s\n", nTotal
/(1000.0*tmElapsed
));
570 int main(int argc
, char **argv
){
571 if( argc
<3 ) showHelp();
572 if( strcmp(argv
[1],"init")==0 ){
573 return initMain(argc
, argv
);
575 if( strcmp(argv
[1],"export")==0 ){
576 return exportMain(argc
, argv
);
578 if( strcmp(argv
[1],"run")==0 ){
579 return runMain(argc
, argv
);