aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-01 19:34:02 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-01 19:34:02 -0800
commita896d6c46c5a2ed6cf28c885d3077df972e7be43 (patch)
treeec57c0c3e9d636136e551f525167d2372f1f993c /src
parent4eed310a459b09d167c85b166472eaaaea1e7db4 (diff)
downloadbun-a896d6c46c5a2ed6cf28c885d3077df972e7be43.tar.gz
bun-a896d6c46c5a2ed6cf28c885d3077df972e7be43.tar.zst
bun-a896d6c46c5a2ed6cf28c885d3077df972e7be43.zip
[`bun:sqlite`] ~15% perf improvement to all() and get()
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/bindings/sqlite/JSSQLStatement.cpp236
1 files changed, 157 insertions, 79 deletions
diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp
index 9873bd400..2ef32f521 100644
--- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp
+++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp
@@ -60,9 +60,6 @@ static void sqlite_free_typed_array(void* ctx, void* buf)
sqlite3_free((void*)buf);
}
-/** This is like a 3x perf improvement for allocating objects **/
-#define SQL_USE_PROTOTYPE 1
-
static int DEFAULT_SQLITE_FLAGS
= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
static unsigned int DEFAULT_SQLITE_PREPARE_FLAGS = SQLITE_PREPARE_PERSISTENT;
@@ -154,6 +151,7 @@ public:
bool hasExecuted = false;
std::unique_ptr<PropertyNameArray> columnNames;
mutable WriteBarrier<JSC::JSObject> _prototype;
+ mutable WriteBarrier<JSC::Structure> _structure;
protected:
JSSQLStatement(JSC::Structure* structure, JSDOMGlobalObject& globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db)
@@ -161,6 +159,7 @@ protected:
, stmt(stmt)
, version_db(version_db)
, columnNames(new PropertyNameArray(globalObject.vm(), PropertyNameMode::Strings, PrivateSymbolMode::Exclude))
+ , _structure(globalObject.vm(), this, nullptr)
, _prototype(globalObject.vm(), this, nullptr)
{
}
@@ -185,50 +184,82 @@ static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQ
auto* stmt = castedThis->stmt;
+ castedThis->_structure.clear();
+ castedThis->_prototype.clear();
+
int count = sqlite3_column_count(stmt);
if (count == 0)
return;
- JSC::ObjectInitializationScope initializationScope(vm);
-
- // 64 is the maximum we can preallocate here
- // see https://github.com/oven-sh/bun/issues/987
- JSC::JSObject* object = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), std::min(count, 64));
- for (int i = 0; i < count; i++) {
- const char* name = sqlite3_column_name(stmt, i);
+ if (count > 63) {
+ JSC::ObjectInitializationScope initializationScope(vm);
- if (name == nullptr)
- break;
+ // 64 is the maximum we can preallocate here
+ // see https://github.com/oven-sh/bun/issues/987
+ JSC::JSObject* object = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), std::min(count, 64));
- size_t len = strlen(name);
- if (len == 0)
- break;
+ for (int i = 0; i < count; i++) {
+ const char* name = sqlite3_column_name(stmt, i);
- auto wtfString = WTF::String::fromUTF8(name, len);
- auto str = JSValue(jsString(vm, wtfString));
- auto key = str.toPropertyKey(lexicalGlobalObject);
- JSC::JSValue primitive = JSC::jsUndefined();
- auto decl = sqlite3_column_decltype(stmt, i);
- if (decl != nullptr) {
- switch (decl[0]) {
- case 'F':
- case 'D':
- case 'I': {
- primitive = jsNumber(0);
+ if (name == nullptr)
break;
- }
- case 'V':
- case 'T': {
- primitive = jsEmptyString(vm);
+
+ size_t len = strlen(name);
+ if (len == 0)
break;
+
+ auto wtfString = WTF::String::fromUTF8(name, len);
+ auto str = JSValue(jsString(vm, wtfString));
+ auto key = str.toPropertyKey(lexicalGlobalObject);
+ JSC::JSValue primitive = JSC::jsUndefined();
+ auto decl = sqlite3_column_decltype(stmt, i);
+ if (decl != nullptr) {
+ switch (decl[0]) {
+ case 'F':
+ case 'D':
+ case 'I': {
+ primitive = jsNumber(0);
+ break;
+ }
+ case 'V':
+ case 'T': {
+ primitive = jsEmptyString(vm);
+ break;
+ }
+ }
}
- }
+
+ object->putDirect(vm, key, primitive, 0);
+ castedThis->columnNames->add(key);
}
+ castedThis->_prototype.set(vm, castedThis, object);
+ } else {
+ // 64 is the maximum we can preallocate here
+ // see https://github.com/oven-sh/bun/issues/987
+ auto& globalObject = *lexicalGlobalObject;
+ Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), count);
+ PropertyOffset offset;
+
+ for (int i = 0; i < count; i++) {
+ const char* name = sqlite3_column_name(stmt, i);
+
+ if (name == nullptr)
+ break;
+
+ size_t len = strlen(name);
+ if (len == 0)
+ break;
- object->putDirect(vm, key, primitive, 0);
- castedThis->columnNames->add(key);
+ auto wtfString = WTF::String::fromUTF8(name, len);
+ auto str = JSValue(jsString(vm, wtfString));
+ auto key = str.toPropertyKey(lexicalGlobalObject);
+ structure = Structure::addPropertyTransition(vm, structure, key, 0, offset);
+ castedThis->columnNames->add(key);
+ }
+ castedThis->_structure.set(vm, castedThis, structure);
+ JSC::JSObject* object = JSC::constructEmptyObject(vm, structure);
+ castedThis->_prototype.set(vm, castedThis, object);
}
- castedThis->_prototype.set(vm, castedThis, object);
}
void JSSQLStatement::destroy(JSC::JSCell* cell)
@@ -978,59 +1009,105 @@ static inline JSC::JSValue constructResultObject(JSC::JSGlobalObject* lexicalGlo
// 64 is the maximum we can preallocate here
// see https://github.com/oven-sh/bun/issues/987
-#if SQL_USE_PROTOTYPE == 1
JSC::JSObject* result;
- if (count <= 64) {
- result = JSC::JSFinalObject::create(vm, castedThis->_prototype.get()->structure());
- } else {
- result = JSC::JSFinalObject::create(vm, JSC::JSFinalObject::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), std::min(count, 64)));
- }
-#else
- JSC::JSObject* result = JSC::JSFinalObject::create(vm, JSC::JSFinalObject::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), std::min(count, 64)));
-#endif
+
auto* stmt = castedThis->stmt;
- for (int i = 0; i < count; i++) {
- auto name = columnNames[i];
+ if (auto* structure = castedThis->_structure.get()) {
+ RELEASE_ASSERT(count <= 64);
+ result = JSC::constructEmptyObject(vm, structure);
- switch (sqlite3_column_type(stmt, i)) {
- case SQLITE_INTEGER: {
- // https://github.com/oven-sh/bun/issues/1536
- result->putDirect(vm, name, jsNumber(sqlite3_column_int64(stmt, i)), 0);
- break;
- }
- case SQLITE_FLOAT: {
- result->putDirect(vm, name, jsNumber(sqlite3_column_double(stmt, i)), 0);
- break;
- }
- // > Note that the SQLITE_TEXT constant was also used in SQLite version
- // > 2 for a completely different meaning. Software that links against
- // > both SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT,
- // > not SQLITE_TEXT.
- case SQLITE3_TEXT: {
- size_t len = sqlite3_column_bytes(stmt, i);
- const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr;
+ for (int i = 0; i < count; i++) {
- if (len > 64) {
- result->putDirect(vm, name, JSC::JSValue::decode(Bun__encoding__toStringUTF8(text, len, lexicalGlobalObject)), 0);
- continue;
+ switch (sqlite3_column_type(stmt, i)) {
+ case SQLITE_INTEGER: {
+ // https://github.com/oven-sh/bun/issues/1536
+ result->putDirectOffset(vm, i, jsNumber(sqlite3_column_int64(stmt, i)));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ result->putDirectOffset(vm, i, jsNumber(sqlite3_column_double(stmt, i)));
+ break;
+ }
+ // > Note that the SQLITE_TEXT constant was also used in SQLite version
+ // > 2 for a completely different meaning. Software that links against
+ // > both SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT,
+ // > not SQLITE_TEXT.
+ case SQLITE3_TEXT: {
+ size_t len = sqlite3_column_bytes(stmt, i);
+ const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr;
+
+ if (len > 64) {
+ result->putDirectOffset(vm, i, JSC::JSValue::decode(Bun__encoding__toStringUTF8(text, len, lexicalGlobalObject)));
+ continue;
+ }
+
+ result->putDirectOffset(vm, i, jsString(vm, WTF::String::fromUTF8(text, len)));
+ break;
+ }
+ case SQLITE_BLOB: {
+ size_t len = sqlite3_column_bytes(stmt, i);
+ const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr;
+ JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), len);
+ memcpy(array->vector(), blob, len);
+ result->putDirectOffset(vm, i, array);
+ break;
+ }
+ default: {
+ result->putDirectOffset(vm, i, jsNull());
+ break;
+ }
}
-
- result->putDirect(vm, name, jsString(vm, WTF::String::fromUTF8(text, len)), 0);
- break;
- }
- case SQLITE_BLOB: {
- size_t len = sqlite3_column_bytes(stmt, i);
- const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr;
- JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), len);
- memcpy(array->vector(), blob, len);
- result->putDirect(vm, name, array, 0);
- break;
}
- default: {
- result->putDirect(vm, name, jsNull(), 0);
- break;
+ } else {
+ if (count <= 64) {
+ result = JSC::JSFinalObject::create(vm, castedThis->_prototype.get()->structure());
+ } else {
+ result = JSC::JSFinalObject::create(vm, JSC::JSFinalObject::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), std::min(count, 64)));
}
+
+ for (int i = 0; i < count; i++) {
+ auto name = columnNames[i];
+
+ switch (sqlite3_column_type(stmt, i)) {
+ case SQLITE_INTEGER: {
+ // https://github.com/oven-sh/bun/issues/1536
+ result->putDirect(vm, name, jsNumber(sqlite3_column_int64(stmt, i)), 0);
+ break;
+ }
+ case SQLITE_FLOAT: {
+ result->putDirect(vm, name, jsNumber(sqlite3_column_double(stmt, i)), 0);
+ break;
+ }
+ // > Note that the SQLITE_TEXT constant was also used in SQLite version
+ // > 2 for a completely different meaning. Software that links against
+ // > both SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT,
+ // > not SQLITE_TEXT.
+ case SQLITE3_TEXT: {
+ size_t len = sqlite3_column_bytes(stmt, i);
+ const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr;
+
+ if (len > 64) {
+ result->putDirect(vm, name, JSC::JSValue::decode(Bun__encoding__toStringUTF8(text, len, lexicalGlobalObject)), 0);
+ continue;
+ }
+
+ result->putDirect(vm, name, jsString(vm, WTF::String::fromUTF8(text, len)), 0);
+ break;
+ }
+ case SQLITE_BLOB: {
+ size_t len = sqlite3_column_bytes(stmt, i);
+ const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr;
+ JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), len);
+ memcpy(array->vector(), blob, len);
+ result->putDirect(vm, name, array, 0);
+ break;
+ }
+ default: {
+ result->putDirect(vm, name, jsNull(), 0);
+ break;
+ }
+ }
}
}
@@ -1506,6 +1583,7 @@ void JSSQLStatement::visitChildrenImpl(JSCell* cell, Visitor& visitor)
JSSQLStatement* thisObject = jsCast<JSSQLStatement*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->_structure);
visitor.append(thisObject->_prototype);
}