Skip to content

Commit 3e3c291

Browse files
committed
[opt] speed acceleration by caching and optimization of sigle-level assignment
1 parent cf5098d commit 3e3c291

File tree

1 file changed

+99
-30
lines changed

1 file changed

+99
-30
lines changed

jdict.m

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,15 @@
173173

174174
% constructor: initialize a jdict object
175175
function obj = jdict(val, varargin)
176-
obj.flags__ = struct('builtinjson', 0);
176+
obj.flags__ = getflags_();
177177
obj.attr = containers.Map();
178178
obj.schema = [];
179179
obj.currentpath__ = char(36);
180180
obj.root__ = obj;
181181
if (nargin >= 1)
182182
if (~isempty(varargin))
183183
allflags = [varargin(1:2:end); varargin(2:2:end)];
184-
obj.flags__ = struct(allflags{:});
184+
obj.flags__ = mergestruct_(obj.flags__, struct(allflags{:}));
185185
if (isfield(obj.flags__, 'attr'))
186186
obj.attr = obj.flags__.attr;
187187
end
@@ -211,7 +211,7 @@
211211

212212
% overloaded numel to prevent subsref from outputting many outputs
213213
function n = numel(obj, varargin)
214-
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
214+
if (obj.flags__.isoctave_)
215215
n = 1;
216216
else
217217
n = max(1, (nargin > 1) + numel(obj.data) * (nargin == 1));
@@ -276,7 +276,7 @@
276276
tempobj.attr = obj.attr;
277277
tempobj.schema = obj.schema;
278278
tempobj.currentpath__ = trackpath;
279-
if (exist('OCTAVE_VERSION', 'builtin') ~= 0 && regexp(OCTAVE_VERSION, '^5\.'))
279+
if (obj.flags__.isoctave_ && regexp(OCTAVE_VERSION, '^5\.'))
280280
val = membercall_(tempobj, idx.subs, idxkey(i + 1).subs{:});
281281
else
282282
fhandle = str2func(idx.subs);
@@ -313,10 +313,7 @@
313313
dimpos = find(strcmp(dims, onekey));
314314
if (~isempty(dimpos) && ~isempty(idxkey(i + 1).subs))
315315
nddata = length(dims);
316-
indices = cell(1, nddata);
317-
for j = 1:nddata
318-
indices{j} = ':';
319-
end
316+
indices = repmat({':'}, 1, nddata);
320317
indices{dimpos(1)} = idxkey(i + 1).subs{1};
321318
subsargs = struct('type', '()', 'subs', {indices});
322319
val = subsref(val, subsargs);
@@ -330,13 +327,14 @@
330327
continue
331328
end
332329
end
333-
escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.');
330+
escapedonekey = esckey_(onekey);
334331
if (ischar(onekey) && ~isempty(onekey) && onekey(1) == char(36))
335332
val = obj.call_('jsonpath', val, onekey);
336333
trackpath = escapedonekey;
337334
elseif (isstruct(val))
338335
% check if struct array - if so, get field from all elements
339-
if (numel(val) > 1 && isfield(val, onekey))
336+
hasfield = isfield(val, onekey);
337+
if (numel(val) > 1 && hasfield)
340338
% struct array - extract field from all elements
341339
val = {val.(onekey)};
342340
if (all(cellfun(@isnumeric, val)) && all(cellfun(@(x) isequal(size(x), size(val{1})), val)))
@@ -355,7 +353,7 @@
355353
i = i + 2;
356354
continue
357355
end
358-
elseif isfield(val, onekey)
356+
elseif hasfield
359357
% single struct with existing field
360358
val = val.(onekey);
361359
trackpath = [trackpath '.' escapedonekey];
@@ -364,7 +362,7 @@
364362
val = [];
365363
trackpath = [trackpath '.' escapedonekey];
366364
end
367-
elseif (isa(val, 'containers.Map') || isa(val, 'dictionary'))
365+
elseif (ismap_(obj.flags__, val))
368366
if isKey(val, onekey)
369367
val = val(onekey);
370368
else
@@ -435,7 +433,7 @@
435433
else
436434
onekey = idx.subs;
437435
end
438-
escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.');
436+
escapedonekey = esckey_(onekey);
439437
if (ischar(onekey) && ~isempty(onekey))
440438
if (onekey(1) ~= char(36))
441439
temppath = [temppath '.' escapedonekey];
@@ -454,7 +452,6 @@
454452

455453
% handle dimension-based assignment like jd.time(1:10) = newval
456454
if (oplen >= 2 && strcmp(idxkey(oplen).type, '()'))
457-
% check if second-to-last is a dimension name
458455
if (strcmp(idxkey(oplen - 1).type, '.') && ischar(idxkey(oplen - 1).subs))
459456
dimname = idxkey(oplen - 1).subs;
460457
% build path to the data
@@ -467,7 +464,7 @@
467464
else
468465
onekey = idx.subs;
469466
end
470-
escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.');
467+
escapedonekey = esckey_(onekey);
471468
if (ischar(onekey) && ~isempty(onekey))
472469
if (onekey(1) ~= char(36))
473470
temppath = [temppath '.' escapedonekey];
@@ -484,10 +481,7 @@
484481
if (~isempty(dimpos) && ~isempty(idxkey(oplen).subs))
485482
% build full indices
486483
nddata = length(dims);
487-
indices = cell(1, nddata);
488-
for j = 1:nddata
489-
indices{j} = ':';
490-
end
484+
indices = repmat({':'}, 1, nddata);
491485
indices{dimpos(1)} = idxkey(oplen).subs{1};
492486
% perform assignment
493487
subsargs = struct('type', '()', 'subs', {indices});
@@ -506,6 +500,36 @@
506500
end
507501
end
508502

503+
% Fast path: single-level assignment like jd.key = value
504+
if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs))
505+
fieldname = idxkey(1).subs;
506+
% Skip if JSONPath
507+
if (isempty(fieldname) || fieldname(1) ~= char(36))
508+
if (isempty(obj.data))
509+
obj.data = struct();
510+
end
511+
if isstruct(obj.data)
512+
try
513+
obj.data.(fieldname) = otherobj;
514+
return
515+
catch
516+
% Field name invalid for struct, convert to Map
517+
fnames = fieldnames(obj.data);
518+
if (~isempty(fnames))
519+
obj.data = containers.Map(fnames, struct2cell(obj.data), 'UniformValues', 0);
520+
else
521+
obj.data = containers.Map;
522+
end
523+
obj.data(fieldname) = otherobj;
524+
return
525+
end
526+
elseif ismap_(obj.flags__, obj.data)
527+
obj.data(fieldname) = otherobj;
528+
return
529+
end
530+
end
531+
end
532+
509533
oplen = length(idxkey);
510534
opcell = cell (1, oplen + 1);
511535
if (isempty(obj.data))
@@ -525,10 +549,10 @@
525549
end
526550
if (ischar(idx.subs) && ~(~isempty(idx.subs) && idx.subs(1) == char(36)))
527551
% Handle empty or non-struct/map data
528-
if isempty(opcell{i}) || (~isstruct(opcell{i}) && ~isa(opcell{i}, 'containers.Map') && ~isa(opcell{i}, 'dictionary'))
552+
if isempty(opcell{i}) || (~isstruct(opcell{i}) && ~ismap_(obj.flags__, opcell{i}))
529553
opcell{i} = obj.newkey_();
530554
end
531-
if (((isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) && ~isKey(opcell{i}, idx.subs)))
555+
if (ismap_(obj.flags__, opcell{i}) && ~isKey(opcell{i}, idx.subs))
532556
idx.type = '()';
533557
opcell{i}(idx.subs) = obj.newkey_();
534558
elseif (isstruct(opcell{i}) && ~isfield(opcell{i}, idx.subs))
@@ -548,21 +572,21 @@
548572
end
549573
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
550574
opcell{i + 1} = obj.call_('jsonpath', opcell{i}, idx.subs);
551-
elseif (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
575+
elseif (ismap_(obj.flags__, opcell{i}))
552576
opcell{i + 1} = opcell{i}(idx.subs);
553577
else
554578
opcell{i + 1} = subsref(opcell{i}, idx);
555579
end
556580
end
557581

558-
if (exist('OCTAVE_VERSION', 'builtin') ~= 0) && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
582+
if (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{i}))
559583
opcell{i}(idx.subs) = otherobj;
560584
opcell{end - 1} = opcell{i};
561585
else
562586
if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36))
563587
opcell{end - 1} = obj.call_('jsonpath', opcell{i}, idx.subs, otherobj);
564588
else
565-
if (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
589+
if (ismap_(obj.flags__, opcell{i}))
566590
idx = struct('type', '()', 'subs', idx.subs);
567591
end
568592
try
@@ -576,7 +600,7 @@
576600

577601
for i = oplen - 1:-1:1
578602
idx = idxkey(i);
579-
if (ischar(idx.subs) && strcmp(idx.type, '.') && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')))
603+
if (ischar(idx.subs) && strcmp(idx.type, '.') && ismap_(obj.flags__, opcell{i}))
580604
idx.type = '()';
581605
end
582606

@@ -596,7 +620,7 @@
596620
opcell{i} = obj.call_('jsonpath', opcell{i}, idx.subs, opcell{i + 1});
597621
else
598622
try
599-
if (exist('OCTAVE_VERSION', 'builtin') ~= 0) && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))
623+
if (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{i}))
600624
opcell{i}(idx.subs) = opcell{i + 1};
601625
else
602626
opcell{i} = subsasgn(opcell{i}, idx, opcell{i + 1});
@@ -624,12 +648,11 @@
624648
function val = keys(obj)
625649
if (isstruct(obj.data))
626650
val = builtin('fieldnames', obj.data);
627-
elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary'))
651+
elseif (ismap_(obj.flags__, obj.data))
628652
val = keys(obj.data);
629653
else
630654
val = 1:length(obj.data);
631655
end
632-
val = val(:);
633656
end
634657

635658
function val = fieldnames(obj)
@@ -641,7 +664,7 @@
641664
% list subfields at the current level
642665
if (isstruct(obj.data))
643666
val = isfield(obj.data, key);
644-
elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary'))
667+
elseif (ismap_(obj.flags__, obj.data))
645668
val = isKey(obj.data, key);
646669
else
647670
val = (key < length(obj.data));
@@ -675,7 +698,7 @@
675698

676699
% internal: insert new key if does not exist
677700
function val = newkey_(obj)
678-
if (exist('containers.Map'))
701+
if (obj.flags__.hasmap_)
679702
val = containers.Map;
680703
else
681704
val = struct;
@@ -1027,3 +1050,49 @@
10271050

10281051
end
10291052
end
1053+
1054+
%% ========================================================================
1055+
%% Helper functions (outside class for persistent caching)
1056+
%% ========================================================================
1057+
1058+
% cached platform flags - called once per session
1059+
function flags = getflags_()
1060+
persistent cachedflags
1061+
if isempty(cachedflags)
1062+
cachedflags = struct();
1063+
cachedflags.builtinjson = 0;
1064+
cachedflags.isoctave_ = exist('OCTAVE_VERSION', 'builtin') ~= 0;
1065+
try
1066+
containers.Map();
1067+
cachedflags.hasmap_ = true;
1068+
catch
1069+
cachedflags.hasmap_ = false;
1070+
end
1071+
try
1072+
dictionary();
1073+
cachedflags.hasdict_ = true;
1074+
catch
1075+
cachedflags.hasdict_ = false;
1076+
end
1077+
end
1078+
flags = cachedflags;
1079+
end
1080+
1081+
% merge two structs
1082+
function s = mergestruct_(base, override)
1083+
s = base;
1084+
fnames = fieldnames(override);
1085+
for i = 1:length(fnames)
1086+
s.(fnames{i}) = override.(fnames{i});
1087+
end
1088+
end
1089+
1090+
% check if value is a map type
1091+
function tf = ismap_(flags, val)
1092+
tf = isa(val, 'containers.Map') || (flags.hasdict_ && isa(val, 'dictionary'));
1093+
end
1094+
1095+
% escape dots in key for JSONPath
1096+
function escaped = esckey_(key)
1097+
escaped = regexprep(key, '(?<=[^\\]|^)\.', '\\.');
1098+
end

0 commit comments

Comments
 (0)