Skip to content

Commit c451ec8

Browse files
committed
Reduce memory allocations in MySQL DAL
1 parent c685abc commit c451ec8

File tree

3 files changed

+83
-77
lines changed

3 files changed

+83
-77
lines changed

src/samples/techempower/database.c

+64-55
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* USA.
1919
*/
2020

21+
#include <assert.h>
2122
#include <string.h>
2223
#include <mysql.h>
2324
#include <sqlite3.h>
@@ -30,17 +31,19 @@
3031

3132
struct db_stmt {
3233
bool (*bind)(const struct db_stmt *stmt,
33-
struct db_row *rows,
34-
size_t n_rows);
35-
bool (*step)(const struct db_stmt *stmt, const char *signature, va_list ap);
34+
struct db_row *rows);
35+
bool (*step)(const struct db_stmt *stmt, va_list ap);
3636
void (*finalize)(struct db_stmt *stmt);
37+
const char *param_signature;
38+
const char *result_signature;
3739
};
3840

3941
struct db {
4042
void (*disconnect)(struct db *db);
4143
struct db_stmt *(*prepare)(const struct db *db,
4244
const char *sql,
43-
const size_t sql_len);
45+
const char *param_signature,
46+
const char *result_signature);
4447
};
4548

4649
/* MySQL */
@@ -56,28 +59,23 @@ struct db_stmt_mysql {
5659
MYSQL_BIND *param_bind;
5760
MYSQL_BIND *result_bind;
5861
bool must_execute_again;
62+
bool results_are_bound;
63+
MYSQL_BIND param_result_bind[];
5964
};
6065

6166
static bool db_stmt_bind_mysql(const struct db_stmt *stmt,
62-
struct db_row *rows,
63-
size_t n_rows)
67+
struct db_row *rows)
6468
{
6569
struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
70+
const char *signature = stmt->param_signature;
6671

6772
stmt_mysql->must_execute_again = true;
73+
mysql_stmt_reset(stmt_mysql->stmt);
6874

69-
if (!stmt_mysql->param_bind) {
70-
stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND));
71-
if (!stmt_mysql->param_bind)
72-
return false;
73-
} else {
74-
mysql_stmt_reset(stmt_mysql->stmt);
75-
}
76-
77-
for (size_t row = 0; row < n_rows && rows[row].kind; row++) {
75+
for (size_t row = 0; signature[row]; row++) {
7876
MYSQL_BIND *param = &stmt_mysql->param_bind[row];
7977

80-
switch (rows[row].kind) {
78+
switch (signature[row]) {
8179
case 's':
8280
param->buffer_type = MYSQL_TYPE_STRING;
8381
param->buffer = rows[row].u.s;
@@ -98,28 +96,24 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt,
9896
}
9997

10098
static bool db_stmt_step_mysql(const struct db_stmt *stmt,
101-
const char *signature,
10299
va_list ap)
103100
{
104101
struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
105102

106103
if (stmt_mysql->must_execute_again) {
107104
stmt_mysql->must_execute_again = false;
105+
stmt_mysql->results_are_bound = false;
108106
if (mysql_stmt_execute(stmt_mysql->stmt))
109107
return false;
110108
}
111109

112-
if (!stmt_mysql->result_bind) {
113-
if (*signature == '\0')
114-
return false;
110+
if (!stmt_mysql->results_are_bound) {
111+
const char *signature = stmt->result_signature;
115112

116-
stmt_mysql->result_bind =
117-
calloc(strlen(signature), sizeof(*stmt_mysql->result_bind));
118-
if (!stmt_mysql->result_bind)
113+
if (*signature == '\0')
119114
return false;
120115

121-
free(stmt_mysql->param_bind);
122-
stmt_mysql->param_bind = NULL;
116+
stmt_mysql->results_are_bound = true;
123117

124118
MYSQL_BIND *result = stmt_mysql->result_bind;
125119
for (size_t r = 0; signature[r]; r++) {
@@ -148,9 +142,7 @@ static bool db_stmt_step_mysql(const struct db_stmt *stmt,
148142
return mysql_stmt_fetch(stmt_mysql->stmt) == 0;
149143

150144
out:
151-
free(stmt_mysql->result_bind);
152-
stmt_mysql->result_bind = NULL;
153-
145+
stmt_mysql->results_are_bound = false;
154146
return false;
155147
}
156148

@@ -159,16 +151,18 @@ static void db_stmt_finalize_mysql(struct db_stmt *stmt)
159151
struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
160152

161153
mysql_stmt_close(stmt_mysql->stmt);
162-
free(stmt_mysql->result_bind);
163-
free(stmt_mysql->param_bind);
164154
free(stmt_mysql);
165155
}
166156

167157
static struct db_stmt *
168-
db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len)
158+
db_prepare_mysql(const struct db *db,
159+
const char *sql,
160+
const char *param_signature,
161+
const char *result_signature)
169162
{
170163
const struct db_mysql *db_mysql = (const struct db_mysql *)db;
171-
struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql));
164+
const size_t n_bounds = strlen(param_signature) + strlen(result_signature);
165+
struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql) + n_bounds * sizeof(MYSQL_BIND));
172166

173167
if (!stmt_mysql)
174168
return NULL;
@@ -177,15 +171,24 @@ db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len)
177171
if (!stmt_mysql->stmt)
178172
goto out_free_stmt;
179173

180-
if (mysql_stmt_prepare(stmt_mysql->stmt, sql, sql_len))
174+
if (mysql_stmt_prepare(stmt_mysql->stmt, sql, strlen(sql)))
181175
goto out_close_stmt;
182176

177+
assert(strlen(param_signature) == mysql_stmt_param_count(stmt_mysql->stmt));
178+
assert(strlen(result_signature) == mysql_stmt_field_count(stmt_mysql->stmt));
179+
183180
stmt_mysql->base.bind = db_stmt_bind_mysql;
184181
stmt_mysql->base.step = db_stmt_step_mysql;
185182
stmt_mysql->base.finalize = db_stmt_finalize_mysql;
186-
stmt_mysql->result_bind = NULL;
187-
stmt_mysql->param_bind = NULL;
183+
stmt_mysql->param_bind = &stmt_mysql->param_result_bind[0];
184+
stmt_mysql->result_bind = &stmt_mysql->param_result_bind[strlen(param_signature)];
188185
stmt_mysql->must_execute_again = true;
186+
stmt_mysql->results_are_bound = false;
187+
188+
stmt_mysql->base.param_signature = param_signature;
189+
stmt_mysql->base.result_signature = result_signature;
190+
191+
memset(stmt_mysql->param_result_bind, 0, n_bounds * sizeof(MYSQL_BIND));
189192

190193
return (struct db_stmt *)stmt_mysql;
191194

@@ -252,26 +255,26 @@ struct db_stmt_sqlite {
252255
};
253256

254257
static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
255-
struct db_row *rows,
256-
size_t n_rows)
258+
struct db_row *rows)
257259
{
258260
const struct db_stmt_sqlite *stmt_sqlite =
259261
(const struct db_stmt_sqlite *)stmt;
262+
const char *signature = stmt->param_signature;
260263

261264
sqlite3_reset(stmt_sqlite->sqlite);
262265
sqlite3_clear_bindings(stmt_sqlite->sqlite);
263266

264-
for (size_t row = 1; row <= n_rows; row++) {
265-
const struct db_row *r = &rows[row - 1];
267+
for (size_t row = 0; signature[row]; row++) {
268+
const struct db_row *r = &rows[row];
266269
int ret;
267270

268-
switch (r->kind) {
271+
switch (signature[row]) {
269272
case 's':
270-
ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1,
273+
ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row + 1, r->u.s, -1,
271274
NULL);
272275
break;
273276
case 'i':
274-
ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i);
277+
ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row + 1, r->u.i);
275278
break;
276279
default:
277280
return false;
@@ -285,11 +288,11 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
285288
}
286289

287290
static bool db_stmt_step_sqlite(const struct db_stmt *stmt,
288-
const char *signature,
289291
va_list ap)
290292
{
291293
const struct db_stmt_sqlite *stmt_sqlite =
292294
(const struct db_stmt_sqlite *)stmt;
295+
const char *signature = stmt->result_signature;
293296

294297
if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW)
295298
return false;
@@ -326,15 +329,18 @@ static void db_stmt_finalize_sqlite(struct db_stmt *stmt)
326329
}
327330

328331
static struct db_stmt *
329-
db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len)
332+
db_prepare_sqlite(const struct db *db,
333+
const char *sql,
334+
const char *param_signature,
335+
const char *result_signature)
330336
{
331337
const struct db_sqlite *db_sqlite = (const struct db_sqlite *)db;
332338
struct db_stmt_sqlite *stmt_sqlite = malloc(sizeof(*stmt_sqlite));
333339

334340
if (!stmt_sqlite)
335341
return NULL;
336342

337-
int ret = sqlite3_prepare_v2(db_sqlite->sqlite, sql, (int)sql_len,
343+
int ret = sqlite3_prepare_v2(db_sqlite->sqlite, sql, (int)strlen(sql),
338344
&stmt_sqlite->sqlite, NULL);
339345
if (ret != SQLITE_OK) {
340346
free(stmt_sqlite);
@@ -345,6 +351,9 @@ db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len)
345351
stmt_sqlite->base.step = db_stmt_step_sqlite;
346352
stmt_sqlite->base.finalize = db_stmt_finalize_sqlite;
347353

354+
stmt_sqlite->base.param_signature = param_signature;
355+
stmt_sqlite->base.result_signature = result_signature;
356+
348357
return (struct db_stmt *)stmt_sqlite;
349358
}
350359

@@ -384,20 +393,18 @@ db_connect_sqlite(const char *path, bool read_only, const char *pragmas[])
384393

385394
/* Generic */
386395

387-
inline bool
388-
db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows)
396+
inline bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows)
389397
{
390-
return stmt->bind(stmt, rows, n_rows);
398+
return stmt->bind(stmt, rows);
391399
}
392400

393-
inline bool
394-
db_stmt_step(const struct db_stmt *stmt, const char *signature, ...)
401+
inline bool db_stmt_step(const struct db_stmt *stmt, ...)
395402
{
396403
va_list ap;
397404
bool ret;
398405

399-
va_start(ap, signature);
400-
ret = stmt->step(stmt, signature, ap);
406+
va_start(ap, stmt);
407+
ret = stmt->step(stmt, ap);
401408
va_end(ap);
402409

403410
return ret;
@@ -407,8 +414,10 @@ inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); }
407414

408415
inline void db_disconnect(struct db *db) { db->disconnect(db); }
409416

410-
inline struct db_stmt *
411-
db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len)
417+
inline struct db_stmt *db_prepare_stmt(const struct db *db,
418+
const char *sql,
419+
const char *param_signature,
420+
const char *result_signature)
412421
{
413-
return db->prepare(db, sql, sql_len);
422+
return db->prepare(db, sql, param_signature, result_signature);
414423
}

src/samples/techempower/database.h

+11-11
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,24 @@ struct db_row {
3030
char *s;
3131
int i;
3232
} u;
33-
char kind; /* 's' = string, 'i' = 'int', '\0' = last */
3433
size_t buffer_length;
3534
};
3635

37-
bool db_stmt_bind(const struct db_stmt *stmt,
38-
struct db_row *rows,
39-
size_t n_rows);
40-
41-
bool db_stmt_step(const struct db_stmt *stmt, const char *signature, ...);
4236

37+
struct db_stmt *db_prepare_stmt(const struct db *db,
38+
const char *sql,
39+
const char *param_signature,
40+
const char *result_signature);
4341
void db_stmt_finalize(struct db_stmt *stmt);
44-
void db_disconnect(struct db *db);
45-
struct db_stmt *
46-
db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len);
4742

48-
struct db *
49-
db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]);
43+
bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows);
44+
bool db_stmt_step(const struct db_stmt *stmt, ...);
45+
46+
struct db *db_connect_sqlite(const char *path,
47+
bool read_only,
48+
const char *pragmas[]);
5049
struct db *db_connect_mysql(const char *host,
5150
const char *user,
5251
const char *pass,
5352
const char *database);
53+
void db_disconnect(struct db *db);

src/samples/techempower/techempower.c

+8-11
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,13 @@ LWAN_HANDLER(json)
196196

197197
static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key)
198198
{
199-
struct db_row row = {.kind = 'i', .u.i = key + 1};
200-
if (UNLIKELY(!db_stmt_bind(stmt, &row, 1)))
199+
struct db_row row = {.u.i = key + 1};
200+
if (UNLIKELY(!db_stmt_bind(stmt, &row)))
201201
return false;
202202

203203
long random_number;
204204
long id;
205-
if (UNLIKELY(!db_stmt_step(stmt, "ii", &random_number, &id)))
205+
if (UNLIKELY(!db_stmt_step(stmt, &random_number, &id)))
206206
return false;
207207

208208
out->id = (int)id;
@@ -219,8 +219,7 @@ static inline bool db_query(struct db_stmt *stmt, struct db_json *out)
219219

220220
LWAN_HANDLER(db)
221221
{
222-
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
223-
sizeof(random_number_query) - 1);
222+
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii");
224223
struct db_json db_json;
225224

226225
if (UNLIKELY(!stmt)) {
@@ -254,8 +253,7 @@ LWAN_HANDLER(queries)
254253
enum lwan_http_status ret = HTTP_INTERNAL_ERROR;
255254
long queries = get_number_of_queries(request);
256255

257-
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
258-
sizeof(random_number_query) - 1);
256+
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii");
259257
if (UNLIKELY(!stmt))
260258
return HTTP_INTERNAL_ERROR;
261259

@@ -295,8 +293,7 @@ static struct cache_entry *cached_queries_new(const void *keyptr, void *context)
295293
if (UNLIKELY(!entry))
296294
return NULL;
297295

298-
stmt = db_prepare_stmt(get_db(), cached_random_number_query,
299-
sizeof(cached_random_number_query) - 1);
296+
stmt = db_prepare_stmt(get_db(), cached_random_number_query, "i", "ii");
300297
if (UNLIKELY(!stmt)) {
301298
free(entry);
302299
return NULL;
@@ -392,15 +389,15 @@ static int fortune_list_generator(struct coro *coro, void *data)
392389
struct fortune_array fortunes;
393390
struct db_stmt *stmt;
394391

395-
stmt = db_prepare_stmt(get_db(), fortune_query, sizeof(fortune_query) - 1);
392+
stmt = db_prepare_stmt(get_db(), fortune_query, "", "is");
396393
if (UNLIKELY(!stmt))
397394
return 0;
398395

399396
fortune_array_init(&fortunes);
400397

401398
long id;
402399
char fortune_buffer[256];
403-
while (db_stmt_step(stmt, "is", &id, &fortune_buffer, sizeof(fortune_buffer))) {
400+
while (db_stmt_step(stmt, &id, &fortune_buffer, sizeof(fortune_buffer))) {
404401
if (!append_fortune(coro, &fortunes, (int)id, fortune_buffer))
405402
goto out;
406403
}

0 commit comments

Comments
 (0)