leptris 1.9.174.5 → 1.9.174.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/TODO.perf/27-c-yield-traversal.md +38 -0
- data/TODO.perf/28-address-fills.md +25 -0
- data/ext/leptris/native/native.c +241 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/cdata.rb +5 -1
- data/lib/leptris/xml/comment.rb +5 -1
- data/lib/leptris/xml/element.rb +3 -1
- data/lib/leptris/xml/node.rb +24 -4
- data/lib/leptris/xml/processing_instruction.rb +11 -3
- data/lib/leptris/xml/text.rb +5 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ac7143c5a7730e13077c52448c2b90a7705959a4fe458e7c29e7782d7ee6df1
|
|
4
|
+
data.tar.gz: 23d3530348907214d341704fa7b098728fa8b677422c8f595e12ae33c2b88ff3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6395fec9ab3701df1b77f32bbe950feff8b918b9f79374b5c107d9e282bb5e9e5fdd91c152dc46526fc201910e69b7c57e8ee968d11e222be3cfdd346c6c7ab8
|
|
7
|
+
data.tar.gz: a55e0b100034203af05855e8881fbfa161d30d6043b3831080ebff8d0f9ca658ca74fd11e1d30aef6887b3108493e4f690f0e08133ca4cf3bca36399cbcacdc4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.174.6] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **C-yield traversal (TODO.perf/27)**: `traverse` and `visit`
|
|
13
|
+
no longer allocate an FFI::Function closure per call — the
|
|
14
|
+
ext's callbacks rb_yield directly, preserving post-order +
|
|
15
|
+
abort-at-self + stash-abort-raise (traverse) and the
|
|
16
|
+
(node, entering, depth) visit contract. Measured (load 34):
|
|
17
|
+
**traverse over 8k nodes 1663.5µs → 379.9µs (4.4x; 6.81x
|
|
18
|
+
Nokogiri); visit 2288.6µs → 545.4µs (4.2x)**.
|
|
19
|
+
- **Address-based first-touch fills (TODO.perf/28)**:
|
|
20
|
+
text/comment/CDATA/PI content and `path` fill through address
|
|
21
|
+
faces (scope-eligible); the bulk attribute faces accept
|
|
22
|
+
scope-owned elements.
|
|
23
|
+
|
|
8
24
|
## [1.9.174.5] - 2026-09-15
|
|
9
25
|
|
|
10
26
|
### Changed
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 27 — C-yield traversal (traverse/visit without the FFI::Function closure)
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.6)
|
|
4
|
+
|
|
5
|
+
Node#traverse and #visit allocate an FFI::Function closure per
|
|
6
|
+
call (~1µs) and dispatch every visited node back through the
|
|
7
|
+
Ruby closure — the dominant per-node cost of whole-subtree
|
|
8
|
+
iteration, paid even on small subtrees where the walk itself is
|
|
9
|
+
microseconds.
|
|
10
|
+
|
|
11
|
+
Bind both to C callbacks that rb_yield directly: the ext's
|
|
12
|
+
traverse face keeps the abort-at-self post-order contract and
|
|
13
|
+
the exception discipline (rb_protect in the callback stashes,
|
|
14
|
+
aborts the walk, re-raises after — exactly the Ruby version's
|
|
15
|
+
contract); visit keeps its (node, entering, depth) callback
|
|
16
|
+
shape with the same stash-and-raise treatment. Node wrapping per
|
|
17
|
+
visit rides the shared binding construction (identity cache +
|
|
18
|
+
kind dispatch), and the walk state lives on the C stack — no
|
|
19
|
+
globals, nested traversals safe.
|
|
20
|
+
|
|
21
|
+
Gates: post-order sequence identical (receiver last); the
|
|
22
|
+
subtree boundary holds (abort-at-self); block exceptions
|
|
23
|
+
re-raise after the walk stops; enum_for forms unchanged; suite
|
|
24
|
+
both modes.
|
|
25
|
+
|
|
26
|
+
## Outcome (1.9.174.6)
|
|
27
|
+
|
|
28
|
+
Native.traverse_binding / visit_binding: C callbacks that
|
|
29
|
+
rb_yield directly (block forwarded explicitly — rb_yield needs
|
|
30
|
+
it on the C entry's frame). traverse keeps post-order +
|
|
31
|
+
abort-at-self + stash-abort-raise (rb_protect in the callback,
|
|
32
|
+
walk state on the C stack — nested traversals independent);
|
|
33
|
+
visit keeps (node, entering, depth) with the engine's
|
|
34
|
+
(user, node, ...) callback order. Two bugs found by the suite:
|
|
35
|
+
the visit callback's argument order (user first — the segfault
|
|
36
|
+
spec caught it) and the block forwarding. Measured (load 34):
|
|
37
|
+
traverse over 8k nodes 1663.5µs (FFI closure) -> 379.9µs
|
|
38
|
+
(4.4x; 6.81x Nokogiri); visit 2288.6µs -> 545.4µs (4.2x).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 28 — Address-based first-touch fills for the remaining reads
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.6)
|
|
4
|
+
|
|
5
|
+
The memoized content readers still fill through FFI marshaling on
|
|
6
|
+
first touch — Text/Comment/CDATA/PI content, and Node#path
|
|
7
|
+
(owned-string read + free) — and the bulk attribute faces exclude
|
|
8
|
+
scope-owned elements though they touch no document state. Add the
|
|
9
|
+
trivial address faces (fast_text/comment/cdata content, pi
|
|
10
|
+
target/data, node path with the free protocol) and switch the
|
|
11
|
+
fills to @addr_reads_fast (scope-eligible); the attributes bulk
|
|
12
|
+
path follows.
|
|
13
|
+
|
|
14
|
+
Gates: identical values for every kind (UTF-8, escapes, nils);
|
|
15
|
+
scope-owned fills answer the same; path memo unchanged; suite
|
|
16
|
+
both modes.
|
|
17
|
+
|
|
18
|
+
## Outcome (1.9.174.6)
|
|
19
|
+
|
|
20
|
+
fast_text/comment/cdata content, fast_pi_target/data (the
|
|
21
|
+
read_pi_data strip preserved), and fast_path (owned-string copy
|
|
22
|
+
+ engine free) — every first-touch content fill now rides the
|
|
23
|
+
address faces via @addr_reads_fast (scope-eligible), and the
|
|
24
|
+
bulk attribute faces accept scope-owned elements (they touch no
|
|
25
|
+
document state; the scope's version stamps the memo).
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -87,8 +87,18 @@ static node_str_fn f_comment_content, f_cdata_content, f_pi_target,
|
|
|
87
87
|
f_pi_data;
|
|
88
88
|
typedef int (*set_str_fn)(void *, const char *);
|
|
89
89
|
typedef int (*node_unlink_fn)(void *);
|
|
90
|
+
typedef int (*traverse_cb_fn)(void *, void *);
|
|
91
|
+
typedef int (*traverse_fn)(void *, int, traverse_cb_fn, void *);
|
|
92
|
+
typedef void (*visit_cb_fn)(void *, void *, int, int);
|
|
93
|
+
typedef void (*visit_fn)(void *, visit_cb_fn, void *);
|
|
94
|
+
typedef void (*free_str_fn)(void *);
|
|
95
|
+
typedef const char *(*node_xpath_fn)(void *);
|
|
90
96
|
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
91
97
|
static node_unlink_fn f_node_unlink;
|
|
98
|
+
static traverse_fn f_node_traverse;
|
|
99
|
+
static visit_fn f_node_visit;
|
|
100
|
+
static free_str_fn f_free_str;
|
|
101
|
+
static node_xpath_fn f_node_xpath;
|
|
92
102
|
static set_root_fn f_set_root;
|
|
93
103
|
static doc_free_fn f_doc_free;
|
|
94
104
|
|
|
@@ -105,6 +115,8 @@ static VALUE c_use_after_free_error;
|
|
|
105
115
|
static VALUE native_cache_of(VALUE document);
|
|
106
116
|
static void resolve_binding_classes(void);
|
|
107
117
|
static VALUE binding_cache_of(VALUE document);
|
|
118
|
+
static VALUE binding_klass_for(int kind);
|
|
119
|
+
static VALUE c_iteration_scope;
|
|
108
120
|
|
|
109
121
|
#define NT_ELEMENT 0
|
|
110
122
|
|
|
@@ -200,6 +212,10 @@ static void resolve_symbols(const char *lib_path)
|
|
|
200
212
|
f_elem_set_text = (set_str_fn)lib_sym(h, "leptris_element_set_text");
|
|
201
213
|
f_text_set_content = (set_str_fn)lib_sym(h, "leptris_text_node_set_content");
|
|
202
214
|
f_node_unlink = (node_unlink_fn)lib_sym(h, "leptris_node_unlink");
|
|
215
|
+
f_node_traverse = (traverse_fn)lib_sym(h, "leptris_node_traverse");
|
|
216
|
+
f_node_visit = (visit_fn)lib_sym(h, "leptris_node_visit");
|
|
217
|
+
f_free_str = (free_str_fn)lib_sym(h, "leptris_free_string");
|
|
218
|
+
f_node_xpath = (node_xpath_fn)lib_sym(h, "leptris_node_get_xpath");
|
|
203
219
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
204
220
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
205
221
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -211,6 +227,8 @@ static void resolve_symbols(const char *lib_path)
|
|
|
211
227
|
!f_comment_content || !f_cdata_content || !f_pi_target ||
|
|
212
228
|
!f_pi_data || !f_elem_set_name || !f_elem_set_text ||
|
|
213
229
|
!f_text_set_content || !f_node_unlink ||
|
|
230
|
+
!f_node_traverse || !f_node_visit || !f_free_str ||
|
|
231
|
+
!f_node_xpath ||
|
|
214
232
|
!f_doc_free ||
|
|
215
233
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
216
234
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -707,6 +725,211 @@ static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
|
707
725
|
return INT2FIX(st);
|
|
708
726
|
}
|
|
709
727
|
|
|
728
|
+
/* ---- C-yield traversal (TODO.perf/27) ----------------------------
|
|
729
|
+
* Same contracts as the Ruby FFI::Function versions without the
|
|
730
|
+
* per-call closure: traverse is post-order with abort-at-self
|
|
731
|
+
* (the receiver is the LAST node of its subtree in post-order,
|
|
732
|
+
* so stopping at self bounds the walk exactly) and
|
|
733
|
+
* stash-abort-raise exception discipline; visit passes (node,
|
|
734
|
+
* entering, depth). Walk state lives on the caller's C stack —
|
|
735
|
+
* nested traversals are independent. */
|
|
736
|
+
struct trav_state {
|
|
737
|
+
VALUE document;
|
|
738
|
+
VALUE err;
|
|
739
|
+
void *self_ptr;
|
|
740
|
+
int for_visit;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
static VALUE trav_yield_one(VALUE arg)
|
|
744
|
+
{
|
|
745
|
+
return rb_yield(arg);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
static int trav_cb(void *node_ptr, void *user)
|
|
749
|
+
{
|
|
750
|
+
struct trav_state *st = user;
|
|
751
|
+
VALUE cache, key, node;
|
|
752
|
+
int kind, state;
|
|
753
|
+
|
|
754
|
+
if (st->err != Qnil)
|
|
755
|
+
return 1;
|
|
756
|
+
kind = f_node_type(node_ptr);
|
|
757
|
+
cache = binding_cache_of(st->document);
|
|
758
|
+
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
759
|
+
node = rb_hash_aref(cache, key);
|
|
760
|
+
if (NIL_P(node)) {
|
|
761
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
762
|
+
rb_iv_set(node, "@c_address", key);
|
|
763
|
+
rb_iv_set(node, "@document", st->document);
|
|
764
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
765
|
+
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
766
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
767
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
768
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
769
|
+
} else {
|
|
770
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
771
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
772
|
+
rb_iv_set(node, "@pub_document", st->document);
|
|
773
|
+
}
|
|
774
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
775
|
+
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
776
|
+
rb_hash_aset(cache, key, node);
|
|
777
|
+
}
|
|
778
|
+
rb_protect(trav_yield_one, node, &state);
|
|
779
|
+
if (state) {
|
|
780
|
+
st->err = rb_errinfo();
|
|
781
|
+
return 1; /* abort the walk */
|
|
782
|
+
}
|
|
783
|
+
return node_ptr == st->self_ptr ? 1 : 0; /* abort-at-self */
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
static VALUE nf_traverse_binding(VALUE self, VALUE document,
|
|
787
|
+
VALUE addr)
|
|
788
|
+
{
|
|
789
|
+
struct trav_state st;
|
|
790
|
+
|
|
791
|
+
(void)self;
|
|
792
|
+
resolve_binding_classes();
|
|
793
|
+
st.document = document;
|
|
794
|
+
st.err = Qnil;
|
|
795
|
+
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
796
|
+
st.for_visit = 0;
|
|
797
|
+
f_node_traverse(st.self_ptr, 1 /* TRAVERSE_POST_ORDER */,
|
|
798
|
+
trav_cb, &st);
|
|
799
|
+
if (st.err != Qnil)
|
|
800
|
+
rb_exc_raise(st.err);
|
|
801
|
+
return Qnil;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
struct visit_yield_args {
|
|
805
|
+
VALUE node, entering, depth;
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
static VALUE trav_yield_args(VALUE arg)
|
|
809
|
+
{
|
|
810
|
+
struct visit_yield_args *a = (struct visit_yield_args *)arg;
|
|
811
|
+
return rb_yield_values(3, a->node, a->entering, a->depth);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Engine visit callback order is (user, node, entering, depth) —
|
|
815
|
+
* the Ruby closure's |_, node_ptr, entering, depth| mirrors it. */
|
|
816
|
+
static void visit_cb(void *user, void *node_ptr, int entering,
|
|
817
|
+
int depth)
|
|
818
|
+
{
|
|
819
|
+
/* visit's engine signature cannot abort; exceptions are
|
|
820
|
+
* stashed and every later node is skipped — the raise
|
|
821
|
+
* happens after the walk, same observable outcome. */
|
|
822
|
+
struct trav_state *st = user;
|
|
823
|
+
struct visit_yield_args va;
|
|
824
|
+
VALUE cache, key, node;
|
|
825
|
+
int kind, state;
|
|
826
|
+
|
|
827
|
+
if (st->err != Qnil)
|
|
828
|
+
return;
|
|
829
|
+
kind = f_node_type(node_ptr);
|
|
830
|
+
cache = binding_cache_of(st->document);
|
|
831
|
+
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
832
|
+
node = rb_hash_aref(cache, key);
|
|
833
|
+
if (NIL_P(node)) {
|
|
834
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
835
|
+
rb_iv_set(node, "@c_address", key);
|
|
836
|
+
rb_iv_set(node, "@document", st->document);
|
|
837
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
838
|
+
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
839
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
840
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
841
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
842
|
+
} else {
|
|
843
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
844
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
845
|
+
rb_iv_set(node, "@pub_document", st->document);
|
|
846
|
+
}
|
|
847
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
848
|
+
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
849
|
+
rb_hash_aset(cache, key, node);
|
|
850
|
+
}
|
|
851
|
+
va.node = node;
|
|
852
|
+
va.entering = entering ? Qtrue : Qfalse;
|
|
853
|
+
va.depth = INT2NUM(depth);
|
|
854
|
+
rb_protect(trav_yield_args, (VALUE)&va, &state);
|
|
855
|
+
if (state)
|
|
856
|
+
st->err = rb_errinfo();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
static VALUE nf_visit_binding(VALUE self, VALUE document, VALUE addr)
|
|
860
|
+
{
|
|
861
|
+
struct trav_state st;
|
|
862
|
+
|
|
863
|
+
(void)self;
|
|
864
|
+
resolve_binding_classes();
|
|
865
|
+
st.document = document;
|
|
866
|
+
st.err = Qnil;
|
|
867
|
+
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
868
|
+
st.for_visit = 1;
|
|
869
|
+
f_node_visit(st.self_ptr, visit_cb, &st);
|
|
870
|
+
if (st.err != Qnil)
|
|
871
|
+
rb_exc_raise(st.err);
|
|
872
|
+
return Qnil;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* ---- Address-based fills (TODO.perf/28) -------------------------
|
|
876
|
+
* Pure reads of node-local data — no cache, no version — the
|
|
877
|
+
* same shapes the inner_html pass uses. */
|
|
878
|
+
static VALUE nf_fast_text_content(VALUE self, VALUE addr)
|
|
879
|
+
{
|
|
880
|
+
const char *s;
|
|
881
|
+
(void)self;
|
|
882
|
+
s = f_text_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
883
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
static VALUE nf_fast_comment_content(VALUE self, VALUE addr)
|
|
887
|
+
{
|
|
888
|
+
const char *s;
|
|
889
|
+
(void)self;
|
|
890
|
+
s = f_comment_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
891
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
static VALUE nf_fast_cdata_content(VALUE self, VALUE addr)
|
|
895
|
+
{
|
|
896
|
+
const char *s;
|
|
897
|
+
(void)self;
|
|
898
|
+
s = f_cdata_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
899
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
static VALUE nf_fast_pi_target(VALUE self, VALUE addr)
|
|
903
|
+
{
|
|
904
|
+
const char *s;
|
|
905
|
+
(void)self;
|
|
906
|
+
s = f_pi_target((void *)(uintptr_t)NUM2ULL(addr));
|
|
907
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
static VALUE nf_fast_pi_data(VALUE self, VALUE addr)
|
|
911
|
+
{
|
|
912
|
+
const char *s;
|
|
913
|
+
(void)self;
|
|
914
|
+
s = f_pi_data((void *)(uintptr_t)NUM2ULL(addr));
|
|
915
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* Owned string: copy + free through the engine's seam (the
|
|
919
|
+
* read_owned_string protocol). */
|
|
920
|
+
static VALUE nf_fast_path(VALUE self, VALUE addr)
|
|
921
|
+
{
|
|
922
|
+
const char *s;
|
|
923
|
+
VALUE out;
|
|
924
|
+
(void)self;
|
|
925
|
+
s = f_node_xpath((void *)(uintptr_t)NUM2ULL(addr));
|
|
926
|
+
if (!s)
|
|
927
|
+
return Qnil;
|
|
928
|
+
out = rb_utf8_str_new_cstr(s);
|
|
929
|
+
f_free_str((void *)s);
|
|
930
|
+
return out;
|
|
931
|
+
}
|
|
932
|
+
|
|
710
933
|
/* C-bound insert family (TODO.perf/14): prepend / insert-after /
|
|
711
934
|
* insert-before share append's gates + predicate + bump shape.
|
|
712
935
|
* mode: 1 prepend, 2 after, 3 before (anchor = receiver). */
|
|
@@ -1003,6 +1226,8 @@ static VALUE m_native_sentinel; /* module object = fallback marker */
|
|
|
1003
1226
|
static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
1004
1227
|
void *result, void *ptr, int kind,
|
|
1005
1228
|
int idx);
|
|
1229
|
+
static VALUE binding_klass_for(int kind);
|
|
1230
|
+
static VALUE c_iteration_scope;
|
|
1006
1231
|
|
|
1007
1232
|
static VALUE bulk_xpath_array(VALUE document, void *result);
|
|
1008
1233
|
|
|
@@ -1711,6 +1936,22 @@ void Init_native(void)
|
|
|
1711
1936
|
nf_set_binding_text, 3);
|
|
1712
1937
|
rb_define_module_function(m_native, "unlink_binding_node",
|
|
1713
1938
|
nf_unlink_binding_node, 2);
|
|
1939
|
+
rb_define_module_function(m_native, "traverse_binding",
|
|
1940
|
+
nf_traverse_binding, 2);
|
|
1941
|
+
rb_define_module_function(m_native, "visit_binding",
|
|
1942
|
+
nf_visit_binding, 2);
|
|
1943
|
+
rb_define_module_function(m_native, "fast_text_content",
|
|
1944
|
+
nf_fast_text_content, 1);
|
|
1945
|
+
rb_define_module_function(m_native, "fast_comment_content",
|
|
1946
|
+
nf_fast_comment_content, 1);
|
|
1947
|
+
rb_define_module_function(m_native, "fast_cdata_content",
|
|
1948
|
+
nf_fast_cdata_content, 1);
|
|
1949
|
+
rb_define_module_function(m_native, "fast_pi_target",
|
|
1950
|
+
nf_fast_pi_target, 1);
|
|
1951
|
+
rb_define_module_function(m_native, "fast_pi_data",
|
|
1952
|
+
nf_fast_pi_data, 1);
|
|
1953
|
+
rb_define_module_function(m_native, "fast_path",
|
|
1954
|
+
nf_fast_path, 1);
|
|
1714
1955
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
1715
1956
|
nf_fast_inner_xml, 1);
|
|
1716
1957
|
rb_define_module_function(m_native, "doc_handle_attach",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/cdata.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::CDATA < Leptris::XML::Text
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_cdata_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_cdata_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
data/lib/leptris/xml/comment.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::Comment < Leptris::XML::Node
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_comment_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_comment_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -260,7 +260,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
260
260
|
# Require the full Attr-hash face — a cold [] may have set the
|
|
261
261
|
# version with only a partial @attr_values (ruby#150).
|
|
262
262
|
return @attributes if @attributes && memo_hit?(@attributes_version)
|
|
263
|
-
|
|
263
|
+
# TODO.perf/28: the bulk faces touch no document state —
|
|
264
|
+
# scope-eligible via @addr_reads_fast.
|
|
265
|
+
if @addr_reads_fast
|
|
264
266
|
# TODO.perf/10: both memo faces in one C walk.
|
|
265
267
|
result, values = Leptris::XML::Native.bulk_attr_faces(
|
|
266
268
|
@c_address, self)
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -492,6 +492,13 @@ class Leptris::XML::Node
|
|
|
492
492
|
def visit(&block)
|
|
493
493
|
return enum_for(:visit) unless block
|
|
494
494
|
ensure_alive!
|
|
495
|
+
# TODO.perf/27: the ext's callback rb_yields directly — no
|
|
496
|
+
# FFI::Function closure per call.
|
|
497
|
+
if @addr_reads_fast
|
|
498
|
+
Leptris::XML::Native.visit_binding(@document, @c_address,
|
|
499
|
+
&block)
|
|
500
|
+
return self
|
|
501
|
+
end
|
|
495
502
|
document = @document
|
|
496
503
|
visitor = ::FFI::Function.new(
|
|
497
504
|
:void, [:pointer, :pointer, :int, :int], blocking: true) do |_, node_ptr, entering, depth|
|
|
@@ -524,9 +531,18 @@ class Leptris::XML::Node
|
|
|
524
531
|
# exception and returns non-zero, aborting the C walk — without
|
|
525
532
|
# it the FFI dispatch silently swallowed the exception and the
|
|
526
533
|
# walk continued with partially processed data.
|
|
527
|
-
def traverse
|
|
528
|
-
return enum_for(:traverse) unless
|
|
534
|
+
def traverse(&block)
|
|
535
|
+
return enum_for(:traverse) unless block
|
|
529
536
|
ensure_alive!
|
|
537
|
+
# TODO.perf/27: post-order + abort-at-self + stash-abort-raise
|
|
538
|
+
# all preserved in the C callback (walk state on its stack).
|
|
539
|
+
# The block forwards explicitly — rb_yield needs it on the C
|
|
540
|
+
# entry's own frame.
|
|
541
|
+
if @addr_reads_fast
|
|
542
|
+
Leptris::XML::Native.traverse_binding(@document, @c_address,
|
|
543
|
+
&block)
|
|
544
|
+
return self
|
|
545
|
+
end
|
|
530
546
|
error = nil
|
|
531
547
|
self_address = @c_address
|
|
532
548
|
callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
|
|
@@ -547,8 +563,12 @@ class Leptris::XML::Node
|
|
|
547
563
|
def path
|
|
548
564
|
return @path if memo_hit?(@path_version)
|
|
549
565
|
ensure_alive!
|
|
550
|
-
|
|
551
|
-
|
|
566
|
+
result = if @addr_reads_fast
|
|
567
|
+
Leptris::XML::Native.fast_path(@c_address)
|
|
568
|
+
else
|
|
569
|
+
str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(c_ptr)
|
|
570
|
+
str_ptr.null? ? nil : Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
571
|
+
end
|
|
552
572
|
if @document
|
|
553
573
|
@path = result
|
|
554
574
|
@path_version = @document.version
|
|
@@ -4,7 +4,11 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
4
4
|
def name
|
|
5
5
|
return @name if memo_hit?(@name_version)
|
|
6
6
|
ensure_alive!
|
|
7
|
-
result =
|
|
7
|
+
result = if @addr_reads_fast
|
|
8
|
+
Leptris::XML::Native.fast_pi_target(@c_address)
|
|
9
|
+
else
|
|
10
|
+
Leptris::XML::FFI.leptris_pi_node_get_target(c_ptr)
|
|
11
|
+
end
|
|
8
12
|
if @document
|
|
9
13
|
@name = result
|
|
10
14
|
@name_version = @document.version
|
|
@@ -16,8 +20,12 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
16
20
|
def content
|
|
17
21
|
return @content if memo_hit?(@content_version)
|
|
18
22
|
ensure_alive!
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
raw = if @addr_reads_fast
|
|
24
|
+
Leptris::XML::Native.fast_pi_data(@c_address)
|
|
25
|
+
else
|
|
26
|
+
Leptris::XML::FFI.leptris_pi_node_get_data(c_ptr)
|
|
27
|
+
end
|
|
28
|
+
result = Leptris::XML::FFI.read_pi_data(raw).to_s
|
|
21
29
|
if @document
|
|
22
30
|
@content = result
|
|
23
31
|
@content_version = @document.version
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::Text < Leptris::XML::Node
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_text_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_text_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.174.
|
|
4
|
+
version: 1.9.174.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -95,6 +95,8 @@ files:
|
|
|
95
95
|
- TODO.perf/24-immutable-read-lanes.md
|
|
96
96
|
- TODO.perf/25-scope-owned-bulk-path.md
|
|
97
97
|
- TODO.perf/26-post-mutation-memo-seeding.md
|
|
98
|
+
- TODO.perf/27-c-yield-traversal.md
|
|
99
|
+
- TODO.perf/28-address-fills.md
|
|
98
100
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
99
101
|
- TODO.restructure/02-deep-copy-seam.md
|
|
100
102
|
- TODO.restructure/03-evaluation-context-seam.md
|