Skip to content

Commit b7da445

Browse files
authored
Support disabling the Erlang allocator (#357)
Allow disabling the Erlang allocator which can sometimes have worse performance characteristics compared to the default one as demonstrated in #356. In addition to the added automated tests, I manually tested this against the repro file provided in the issue by setting my fork as a dependency and toggling the `disable_erlang_allocator` setting.
1 parent f92573b commit b7da445

4 files changed

Lines changed: 64 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ Once you have `exqlite` configured, you can use the `:key` option in the databas
143143
config :exqlite, key: "super-secret'
144144
```
145145
146+
### Disabling the Erlang allocator
147+
148+
By default, we configure SQLite to use allocators provided by Erlang ([`enif_alloc`](https://www.erlang.org/doc/apps/erts/erl_nif.html#enif_alloc)). This allows SQLite's memory usage to be reported by `:erlang.memory()`. However, in some cases this may perform worse than using SQLite's default allocator, in which case you can disable the Erlang allocator:
149+
150+
```elixir
151+
config :exqlite, disable_erlang_allocator: true
152+
```
153+
154+
Note that this setting is read when the NIF is loaded and subsequent changes will have no effect unless you restart the BEAM VM.
155+
146156
## Usage
147157
148158
The `Exqlite.Sqlite3` module usage is fairly straight forward.

‎c_src/sqlite3_nif.c‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <erl_nif.h>
1111
#include <sqlite3.h>
1212

13+
static ERL_NIF_TERM am_false;
14+
static ERL_NIF_TERM am_true;
1315
static ERL_NIF_TERM am_ok;
1416
static ERL_NIF_TERM am_error;
1517
static ERL_NIF_TERM am_badarg;
@@ -41,6 +43,8 @@ static ERL_NIF_TERM am_update;
4143
static ERL_NIF_TERM am_invalid_pid;
4244
static ERL_NIF_TERM am_log;
4345

46+
static int erlang_allocator_enabled = 0;
47+
4448
static ErlNifResourceType* connection_type = NULL;
4549
static ErlNifResourceType* statement_type = NULL;
4650
static sqlite3_mem_methods default_alloc_methods = {0};
@@ -1435,9 +1439,8 @@ on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
14351439
exqlite_mem_shutdown,
14361440
0};
14371441

1438-
sqlite3_config(SQLITE_CONFIG_GETMALLOC, &default_alloc_methods);
1439-
sqlite3_config(SQLITE_CONFIG_MALLOC, &methods);
1440-
1442+
am_true = enif_make_atom(env, "true");
1443+
am_false = enif_make_atom(env, "false");
14411444
am_ok = enif_make_atom(env, "ok");
14421445
am_error = enif_make_atom(env, "error");
14431446
am_badarg = enif_make_atom(env, "badarg");
@@ -1469,6 +1472,17 @@ on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
14691472
am_invalid_pid = enif_make_atom(env, "invalid_pid");
14701473
am_log = enif_make_atom(env, "log");
14711474

1475+
ERL_NIF_TERM disable_erlang_allocator;
1476+
if (!enif_get_map_value(env, info, enif_make_atom(env, "disable_erlang_allocator"), &disable_erlang_allocator)) {
1477+
return -1;
1478+
}
1479+
erlang_allocator_enabled = enif_is_identical(disable_erlang_allocator, am_false);
1480+
1481+
sqlite3_config(SQLITE_CONFIG_GETMALLOC, &default_alloc_methods);
1482+
if (erlang_allocator_enabled) {
1483+
sqlite3_config(SQLITE_CONFIG_MALLOC, &methods);
1484+
}
1485+
14721486
connection_type = enif_open_resource_type(
14731487
env,
14741488
NULL,
@@ -2069,6 +2083,21 @@ exqlite_errstr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
20692083
return make_binary(env, msg, strlen(msg));
20702084
}
20712085

2086+
//
2087+
// This is only used in tests to verify whether the Erlang allocator is being used.
2088+
//
2089+
ERL_NIF_TERM
2090+
exqlite_erlang_allocator_enabled(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
2091+
{
2092+
assert(env);
2093+
2094+
if (argc != 0) {
2095+
return enif_make_badarg(env);
2096+
}
2097+
2098+
return erlang_allocator_enabled ? am_true : am_false;
2099+
}
2100+
20722101
//
20732102
// Most of our nif functions are going to be IO bounded
20742103
//
@@ -2105,6 +2134,7 @@ static ErlNifFunc nif_funcs[] = {
21052134
{"cancel", 1, exqlite_cancel, 0},
21062135
{"errmsg", 1, exqlite_errmsg},
21072136
{"errstr", 1, exqlite_errstr},
2137+
{"erlang_allocator_enabled", 0, exqlite_erlang_allocator_enabled},
21082138
};
21092139

21102140
ERL_NIF_INIT(Elixir.Exqlite.Sqlite3NIF, nif_funcs, on_load, NULL, on_upgrade, on_unload)

‎lib/exqlite/sqlite3_nif.ex‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ defmodule Exqlite.Sqlite3NIF do
2020

2121
def load_nif() do
2222
path = :filename.join(:code.priv_dir(:exqlite), ~c"sqlite3_nif")
23-
:erlang.load_nif(path, 0)
23+
24+
disable_erlang_allocator =
25+
Application.get_env(:exqlite, :disable_erlang_allocator, false)
26+
27+
if not is_boolean(disable_erlang_allocator) do
28+
raise ArgumentError, ":disable_erlang_allocator must be a boolean"
29+
end
30+
31+
:erlang.load_nif(path, %{disable_erlang_allocator: disable_erlang_allocator})
2432
end
2533

2634
@spec open(String.t(), integer()) :: {:ok, db()} | {:error, reason()}
@@ -117,5 +125,9 @@ defmodule Exqlite.Sqlite3NIF do
117125
@spec errstr(integer) :: String.t()
118126
def errstr(_rc), do: :erlang.nif_error(:not_loaded)
119127

128+
@doc false
129+
@spec erlang_allocator_enabled() :: boolean()
130+
def erlang_allocator_enabled(), do: :erlang.nif_error(:not_loaded)
131+
120132
# add statement inspection tooling https://sqlite.org/c3ref/expanded_sql.html
121133
end

‎test/exqlite/allocator_test.exs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule Exqlite.AllocatorTest do
2+
use ExUnit.Case
3+
4+
test "erlang allocator is used by default" do
5+
assert Application.get_env(:exqlite, :disable_erlang_allocator) == nil
6+
assert Exqlite.Sqlite3NIF.erlang_allocator_enabled()
7+
end
8+
end

0 commit comments

Comments
 (0)