Skip to content

Commit

Permalink
Optimize using iterator and bitset
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Jan 26, 2025
1 parent e3a328c commit 28ecf9d
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 52 deletions.
87 changes: 37 additions & 50 deletions binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ struct BaseIterator {
if (reverse_ ? cmp > 0 : cmp < 0) {
Next();
}
} else {
} else { // TODO: can we skip this code path if not in reverse?
SeekToFirst();
if (dbIterator_->Valid()) {
int cmp = dbIterator_->key().compare(target);
Expand All @@ -893,6 +893,15 @@ struct BaseIterator {
}
}

/**
* Seek to an exact key.
*/
bool SeekExact (leveldb::Slice& target) {
didSeek_ = true;
dbIterator_->Seek(target);
return dbIterator_->Valid() && dbIterator_->key() == target;
}

void CloseIterator () {
if (!hasClosed_) {
hasClosed_ = true;
Expand Down Expand Up @@ -1573,74 +1582,49 @@ struct HasManyWorker final : public PriorityWorker {
Database* database,
std::vector<std::string> keys,
napi_deferred deferred,
uint32_t* bitset,
const bool fillCache,
ExplicitSnapshot* snapshot
) : PriorityWorker(env, database, deferred, "classic_level.has.many"),
keys_(std::move(keys)) {
options_.fill_cache = fillCache;

if (snapshot == NULL) {
implicitSnapshot_ = database->NewSnapshot();
options_.snapshot = implicitSnapshot_;
} else {
implicitSnapshot_ = NULL;
options_.snapshot = snapshot->nut;
}
keys_(std::move(keys)),
bitset_(bitset) {
iterator_ = new BaseIterator(
database,
// Range options (not relevant)
false, NULL, NULL, NULL, NULL, -1,
fillCache,
snapshot
);
}

~HasManyWorker () {
delete iterator_;
}

void DoExecute () override {
cache_.reserve(keys_.size());
for (size_t i = 0; i != keys_.size(); i++) {
leveldb::Slice target = leveldb::Slice(keys_[i]);

for (const std::string& key: keys_) {
std::string value;
leveldb::Status status = database_->Get(options_, key, value);

if (status.ok()) {
cache_.push_back(true);
} else if (status.IsNotFound()) {
cache_.push_back(false);
} else {
SetStatus(status);
break;
if (iterator_->SeekExact(target)) {
bitset_[i >> 5] |= 1 << (i & 31); // Set bit
}
}

if (implicitSnapshot_) {
database_->ReleaseSnapshot(implicitSnapshot_);
}
}

void HandleOKCallback (napi_env env, napi_deferred deferred) override {
size_t size = cache_.size();

napi_value array;
napi_value booleanTrue;
napi_value booleanFalse;

napi_create_array_with_length(env, size, &array);
napi_get_boolean(env, true, &booleanTrue);
napi_get_boolean(env, false, &booleanFalse);

for (size_t i = 0; i < size; i++) {
auto value = cache_[i] ? booleanTrue : booleanFalse;
napi_set_element(env, array, static_cast<uint32_t>(i), value);
}

napi_resolve_deferred(env, deferred, array);
SetStatus(iterator_->Status());
iterator_->CloseIterator();
}

private:
leveldb::ReadOptions options_;
const std::vector<std::string> keys_;
std::vector<bool> cache_;
const leveldb::Snapshot* implicitSnapshot_;
uint32_t* bitset_;
BaseIterator* iterator_;
};

/**
* Check if the database has entries with the given keys.
*/
NAPI_METHOD(db_has_many) {
NAPI_ARGV(4);
NAPI_ARGV(5);
NAPI_DB_CONTEXT();
NAPI_PROMISE();

Expand All @@ -1650,8 +1634,11 @@ NAPI_METHOD(db_has_many) {
ExplicitSnapshot* snapshot = NULL;
napi_get_value_external(env, argv[3], (void**)&snapshot);

uint32_t* bitset = NULL;
NAPI_STATUS_THROWS(napi_get_arraybuffer_info(env, argv[4], (void**)&bitset, NULL));

HasManyWorker* worker = new HasManyWorker(
env, database, keys, deferred, fillCache, snapshot
env, database, keys, deferred, bitset, fillCache, snapshot
);

worker->Queue(env);
Expand Down
19 changes: 17 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,27 @@ class ClassicLevel extends AbstractLevel {
}

async _hasMany (keys, options) {
return binding.db_has_many(
// Use a space-efficient bitset (with 32-bit words) to contain found keys
const wordCount = (keys.length + 32) >>> 5
const buffer = new ArrayBuffer(wordCount * 4)
const bitset = new Uint32Array(buffer)

await binding.db_has_many(
this[kContext],
keys,
options.fillCache,
options.snapshot?.[kContext]
options.snapshot?.[kContext],
buffer
)

const values = new Array(keys.length)

for (let i = 0; i < values.length; i++) {
// Check if bit is set
values[i] = (bitset[i >>> 5] & (1 << (i & 31))) !== 0
}

return values
}

async _del (key, options) {
Expand Down

0 comments on commit 28ecf9d

Please sign in to comment.