Repository navigation
native: shorter Python call path for a point read (port of #525) - #147
Conversation
Port of humemai/arcadedb-embedded-python#525 (closed when the bindings moved here) onto today's _native code. An untimed per-call decomposition on the laptop put about 15 of 34 us of `SELECT .. WHERE id = ? .first()` in Python outside the engine. What changes, behaviour identical: - codec.encode_params: one exact-int or exact-str parameter is written as '{"pos": [v]}' directly (the bytes json.dumps gives) without the set of types and the encoder call; bool, int/str subclasses, floats and everything else keep the general path. - lib.exec_query: arcadedb_n_exec for a text-only parameter list without the by-name getattr, the thread-handle call and the argument tuple; same reply reader as call_bin (a float32 tail still decodes). database._exec uses it, with cached language and statement bytes. - NativeResultSet.first(): when the first batch holds the row and the result is readable, take it and close, without the iterator protocol. Same state afterwards as the iterator path (closed, not marked drained). - Record wrap: the import in NativeResult._record and records._java_object is done once, and _wrap looks the class up in a module dict. Dropped from #525: the lookup_by_key statement cache (lookup_by_key now calls the engine's lookupByKey through arcadedb_n_lookup; it still gains from the first() change), and the shared Java-object stand-in per process (each record keeps its own, only the import is cached). Untimed numbers from the old study (us per call, whole call incl. two get() calls): SELECT .. WHERE id = ? .first() 34.36 -> 29.60; encode_params 2.39 -> 0.58, json_loads 4.86 -> 4.49 (unchanged by this, see the follow-up note on a binary first batch). These are a decomposition on the previous code, not a speed claim: the timed A/B on the current wheel is still to run. tests/test_native_point_read.py (native backend only, collected in conftest): the one-parameter text equals json.dumps and the general encoder for 13 int/str values and 17 other kinds, the point-read rows equal the iterator and to_list() rows (float32 tail, escapes, multi-byte text), first() state and errors, record wrapping per kind, lookup_by_key, and a spy that the short path is the one taken. The two spy tests fail on the previous code, the others pass on both. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Timed A/B on the laptop (the decision rule was written before the run), head
The A/A control is clean on all four figures. By the rule (ship when the point-read figure's upper CI end is below 0.95 and no figure's lower end is above 1.03) the verdict is SHIP: the point read by id is 11 to 13 percent faster, the lookup by key 3 to 5 percent, the 1000-row range and the Cypher count show no loss. Marking this ready; it merges when CI is green. |
Draft: the speed claim waits for the timed A/B on the current native wheel (queued on the laptop, results will be posted here). Only the behaviour is claimed now: results are identical to the long path.
Port of humemai/arcadedb-embedded-python#525 (closed when the bindings moved here), rewritten onto the current
_nativecode. A point read (db.query("sql", "SELECT FROM T WHERE id = ?", v).first()) spends about 15 of 34 us in Python outside the engine in an untimed per-call decomposition on the laptop, so the change shortens that Python part. No Java, no native-image rebuild: the wheel'slibarcadedb.sois the same.Changes:
codec.encode_params: one exactintor exactstrparameter is written as{"pos": [v]}directly (the bytesjson.dumpsgives), without the type-set check and the encoder call.bool, subclasses, floats and everything else take the general path as before.lib.exec_query:arcadedb_n_execfor a text-only parameter list, without the by-namegetattr, the thread-handle call and the argument tuple. It reads the reply ascall_bindoes (a float32 tail still decodes).NativeDatabase._execuses it with cached language and statement bytes (lib.bc).NativeResultSet.first(): when the first batch holds the row and the result is readable, take the row and close, without the iterator protocol. The state afterwards is the iterator path's (closed, not marked drained); a lazy command result, an empty result or a closed database take the old path.recordsimport inNativeResult._recordand thedatabaseimport inrecords._java_objectare done once, and_wrap/_wrap_dictlook the class up in a module dict.Dropped from #525 because current code already covers or changed it: the
lookup_by_keystatement cache (it now calls the engine'slookupByKeythrougharcadedb_n_lookup; it still gains from thefirst()change), and the shared Java-object stand-in (each record keeps its own object, only the import is cached, so identity andsetattrbehaviour do not change).Untimed numbers from the old study (us per call, whole call including two
get()calls):SELECT .. WHERE id = ? .first()34.36 to 29.60,encode_params2.39 to 0.58,json_loads4.86 to 4.49 (not touched here). These describe the previous code and are not a speed claim. The deciding timed figure will besql_by_id_first_us, withlookup_by_key_us,range_1000_rows_usandcypher_2hop_count_usas guards, A/B on the current wheel with an A/A control.Tests:
tests/test_native_point_read.py(native backend only, listed inconftest.py) pins the one-parameter text againstjson.dumpsand the general encoder for 13 int/str values and 17 other kinds, point-read rows against the iterator andto_list()(float32 tail, escapes, multi-byte text),first()state and errors, record wrapping per kind,lookup_by_key, and a spy that the short path is the one taken. Run against the unpacked26.11.1.dev0linux-x86_64 wheel with this PR's four Python files overlaid (they are byte-identical tomainin the wheel): new file 99 passed; with ittest_native_result_transport,test_resultset,test_parameter_binding,test_lookup_by_key_index,test_results_after_close,test_positional_count,test_record_id_values,test_graph_api,test_resultset_read_errors,test_typed_rows,test_percall_callsandtest_native_columnar_insertpassed (the skips are the suite's NA-JVM/NOTYET entries). The two spy tests fail on the unpatched wheel, the rest pass on both. black 26.10.1, isort 9.0.2--profile blackand bandit are clean.🤖 Generated with Claude Code