Skip to content

Commit f569288

Browse files
committed
perf: reduce SAX sliding-window allocations (Phase 3).
Use _paa2 with in-place segment sums, _znorm_slice for window z-norm without copying, and a single _sax_via_window path shared with sax_via_window. Removes deprecated _paa.
1 parent aea6fe6 commit f569288

4 files changed

Lines changed: 77 additions & 208 deletions

File tree

inst/include/jmotif.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,9 @@ std::vector<double> _alphabet_to_cuts(int a_size);
326326
int _count_spaces(std::string *s);
327327
double _mean(std::vector<double> *ts, int *start, int *end);
328328
std::vector<double> _znorm(const std::vector<double>& ts, double threshold);
329-
std::vector<double> _paa(std::vector<double> ts, int paa_num); // deprecated
330-
std::vector<double> _paa2(std::vector<double> ts, int paa_num);
329+
void _znorm_slice(const std::vector<double>& ts, int start, int end,
330+
double threshold, std::vector<double>& out);
331+
std::vector<double> _paa2(const std::vector<double>& ts, int paa_num);
331332
double _euclidean_dist(std::vector<double>* seq1, std::vector<double>* seq2);
332333
double _early_abandoned_dist(std::vector<double>* seq1, std::vector<double>* seq2,
333334
double upper_limit);
@@ -337,7 +338,7 @@ std::string _series_to_string(std::vector<double> ts, int a_size);
337338
bool _is_equal_mindist(std::string a, std::string b);
338339
//
339340
std::unordered_map<int, std::string> _sax_via_window(
340-
std::vector<double> ts, int w_size, int paa_size, int a_size,
341+
const std::vector<double>& ts, int w_size, int paa_size, int a_size,
341342
std::string nr_strategy, double n_threshold);
342343

343344
#endif

src/paa.cpp

Lines changed: 30 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -29,146 +29,54 @@ NumericVector paa(NumericVector ts, int paa_num) {
2929
return wrap(_paa2(Rcpp::as< std::vector<double> >(ts), paa_num));
3030
}
3131

32-
std::vector<double> _paa(std::vector<double> ts, int paa_num) {
32+
std::vector<double> _paa2(const std::vector<double>& ts, int paa_num) {
3333

34-
// fix the length
3534
int len = ts.size();
3635

37-
// check for the trivial case
38-
if (len == paa_num) {
39-
std::vector<double> res(ts);
40-
return res;
41-
}
42-
else {
43-
// if the number of points in a segment is even
44-
if (len % paa_num == 0) {
45-
std::vector<double> res(paa_num, 0);
46-
int inc = len / paa_num;
47-
for (int i = 0; i < len; i++) {
48-
int idx = i / inc; // the spot
49-
res[idx] = res[idx] + ts[i];
50-
}
51-
double dl = (double) inc;
52-
for (int i = 0; i < paa_num; i++) {
53-
res[i] = res[i] / dl;
54-
}
55-
return res;
56-
}else{
57-
// if the number of points in a segment is odd
58-
std::vector<double> res(paa_num);
59-
for (int i = 0; i < len * paa_num; i++) {
60-
int idx = i / len; // the spot
61-
int pos = i / paa_num; // the col spot
62-
res[idx] = res[idx] + ts[pos];
63-
}
64-
double dl = (double) len;
65-
for (int i = 0; i < paa_num; i++) {
66-
res[i] = res[i] / dl;
67-
}
68-
return res;
69-
}
70-
}
71-
}
72-
73-
std::vector<double> _paa2(std::vector<double> ts, int paa_num) {
74-
75-
std::vector<double> tempVector;
76-
77-
// fix the length
78-
int len = ts.size();
79-
// Rcout << "len " << len << std::endl;
80-
8136
if(len < paa_num){
8237
stop("'paa_num' size is invalid");
8338
}
8439

85-
// check for the trivial case
8640
if (len == paa_num) {
87-
std::vector<double> res(ts);
88-
return res;
41+
return ts;
8942
}
90-
else {
9143

92-
// Rcpp::Rcout.precision(5);
93-
// Rcout << std::fixed;
44+
std::vector<double> res(paa_num, 0.0);
45+
double points_per_segment = (double) len / (double) paa_num;
9446

95-
std::vector<double> res(paa_num, 0.0);
96-
double points_per_segment = (double) len / (double) paa_num;
97-
// Rcout << "points per seg " << points_per_segment << std::endl;
47+
std::vector<double> breaks(paa_num + 1, 0);
48+
for(int i = 0; i < paa_num + 1; i++){
49+
breaks[i] = i * points_per_segment;
50+
}
51+
breaks[paa_num] = (double) len;
9852

99-
std::vector<double> breaks(paa_num + 1, 0);
100-
for(int i = 0; i < paa_num + 1; i++){
101-
breaks[i] = i * points_per_segment;
102-
}
103-
// The final break is paa_num * (len/paa_num), which should be exactly len
104-
// but in IEEE-754 often rounds to len + epsilon (e.g. 7*(29/7) =
105-
// 29.000000000000004). That makes floor(seg_end) == len for the last
106-
// segment, so frac_end collapses to ~1e-15 and the last sample is dropped
107-
// from the segment sum while the divisor stays the same -- corrupting the
108-
// final PAA value. Snap it to len so the boundary is handled exactly,
109-
// matching the saxpy / Java fractional PAA.
110-
breaks[paa_num] = (double) len;
111-
// Rcout << "ts length: " << len << ", breaks: ";
112-
// for(auto it=breaks.begin(); it<breaks.end(); ++it){
113-
// Rcout << *it << ", ";
114-
//}
115-
//Rcout << std::endl;
53+
for(int i = 0; i < paa_num; i++){
11654

117-
for(int i = 0; i < paa_num; i++){
55+
double seg_start = breaks[i];
56+
double seg_end = breaks[i+1];
11857

119-
double seg_start = breaks[i];
120-
double seg_end = breaks[i+1];
121-
// Rcout << " * seg_start " << seg_start << ", end " << seg_end << std::endl;
58+
double frac_begin = ceil(seg_start) - seg_start;
59+
double frac_end = seg_end - floor(seg_end);
12260

123-
double frac_begin = ceil(seg_start) - seg_start;
124-
double frac_end = seg_end - floor(seg_end);
125-
// Rcout << " ** frac_begin " << frac_begin << ", frac_end " << frac_end << std::endl;
61+
int full_begin = (int)floor(seg_start);
62+
int full_end = (int)ceil(seg_end);
63+
if(full_end > len) {
64+
full_end = len;
65+
}
12666

127-
int full_begin = floor(seg_start);
128-
int full_end = ceil(seg_end);
129-
if(full_end > len) {
130-
full_end = len;
67+
double sum_of_elems = 0.0;
68+
for (int j = full_begin; j < full_end; j++) {
69+
double v = ts[j];
70+
if (j == full_begin && frac_begin > 0) {
71+
v *= frac_begin;
13172
}
132-
// Rcout << " *** full_begin " << full_begin << ", full_end " << full_end << std::endl;
133-
134-
// the following taken from
135-
// https://stackoverflow.com/questions/421573/best-way-to-extract-a-subvector-from-a-vector
136-
// but it may leak memory?
137-
std::vector<double>::const_iterator first = ts.begin() + full_begin;
138-
std::vector<double>::const_iterator last = ts.begin() + full_end;
139-
std::vector<double> segment(first, last);
140-
//std::vector<double> segment(ts.begin() + full_begin, ts.begin() + full_end);
141-
142-
// Rcout << "segment ";
143-
// for(auto it=segment.begin(); it<segment.end(); ++it){
144-
// Rcout << *it << ", ";
145-
// }
146-
// Rcout << std::endl;
147-
148-
if(frac_begin > 0){
149-
segment[0] = segment[0] * frac_begin;
73+
if (j == full_end - 1 && frac_end > 0) {
74+
v *= frac_end;
15075
}
151-
152-
if(frac_end > 0){
153-
segment[segment.size()-1] = segment[segment.size()-1] * frac_end;
154-
}
155-
// Rcout << "adj_segment ";
156-
// for(auto it=segment.begin(); it<segment.end(); ++it){
157-
// Rcout << *it << ", ";
158-
// }
159-
// Rcout << std::endl;
160-
161-
double sum_of_elems = 0.0;
162-
for (double e : segment)
163-
sum_of_elems += e;
164-
// Rcout << " **** sum " << sum_of_elems << std::endl;
165-
166-
segment.swap(tempVector);
167-
168-
res[i] = sum_of_elems / points_per_segment;
169-
// Rcout << " **** res[" << i << "] " << res[i] << std::endl;
170-
76+
sum_of_elems += v;
17177
}
172-
return res;
78+
79+
res[i] = sum_of_elems / points_per_segment;
17380
}
81+
return res;
17482
}

src/sax.cpp

Lines changed: 13 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,21 @@ std::string _series_to_string(std::vector<double> ts, int a_size) {
236236
}
237237

238238
std::unordered_map<int, std::string> _sax_via_window(
239-
std::vector<double> ts, int w_size, int paa_size, int a_size,
239+
const std::vector<double>& ts, int w_size, int paa_size, int a_size,
240240
std::string nr_strategy, double n_threshold) {
241241

242242
std::unordered_map<int, std::string> idx2word;
243243
bool strategy_exact = is_equal_str("exact", nr_strategy);
244244
bool strategy_mindist = is_equal_str("mindist", nr_strategy);
245245

246246
std::string old_str;
247-
248-
//Rcout << "series length " << ts.size() << ", window " << w_size << std::endl;
247+
std::vector<double> znorm_buf;
248+
znorm_buf.reserve(w_size);
249+
std::vector<double> paa_buf;
250+
paa_buf.reserve(paa_size);
249251

250252
for (unsigned i = 0; i <= ts.size() - w_size; i++) {
251253

252-
// check if NA is encountered
253254
int idx = i + w_size - 1;
254255
if(R_IsNA(ts[idx])) {
255256
size_t size = std::snprintf(nullptr, 0, "encountered an Na and stopped processing at %i", i + w_size - 1) + 1;
@@ -261,17 +262,10 @@ std::unordered_map<int, std::string> _sax_via_window(
261262
break;
262263
}
263264

264-
// subseries extraction
265-
// std::vector<double>::const_iterator first = ts.begin() + i;
266-
// std::vector<double>::const_iterator last = ts.begin() + i + w_size;
267-
// std::vector<double> subSection(first, last);
268-
std::vector<double> subSection = _subseries(&ts, i, i + w_size);
269-
270-
subSection = _znorm(subSection, n_threshold);
271-
272-
subSection = _paa(subSection, paa_size);
265+
_znorm_slice(ts, (int)i, (int)i + w_size, n_threshold, znorm_buf);
266+
paa_buf = _paa2(znorm_buf, paa_size);
273267

274-
std::string curr_str = _series_to_string(subSection, a_size);
268+
std::string curr_str = _series_to_string(paa_buf, a_size);
275269

276270
if (!(old_str.empty())) {
277271
if (strategy_exact && old_str==curr_str) {
@@ -287,7 +281,6 @@ std::unordered_map<int, std::string> _sax_via_window(
287281
old_str = curr_str;
288282
}
289283

290-
291284
return idx2word;
292285
}
293286

@@ -319,74 +312,11 @@ std::map<int, std::string> sax_via_window(
319312
forward_exception_to_r(ex);
320313
}
321314

322-
std::vector<double> series = Rcpp::as< std::vector<double> > (ts);
323-
bool strategy_exact = is_equal_str("exact", nr_strategy);
324-
bool strategy_mindist = is_equal_str("mindist", nr_strategy);
325-
326-
std::map<int, std::string> idx2word;
327-
328-
// std::ofstream bw("test_outR.txt");
329-
330-
std::string old_str("");
331-
332-
for (unsigned i = 0; i <= series.size() - w_size; i++) {
333-
334-
// check if NA is encountered
335-
int idx = i + w_size - 1;
336-
if(R_IsNA(ts[idx])) {
337-
size_t size = std::snprintf(nullptr, 0, "encountered an Na and stopped processing at %i", i + w_size - 1) + 1;
338-
std::unique_ptr<char[]> buf( new char[ size ] );
339-
std::snprintf( buf.get(), size, "encountered an Na and stopped processing at %i", i + w_size - 1 );
340-
Rcpp::warning(
341-
std::string( buf.get(), buf.get() + size - 1 )
342-
);
343-
break;
344-
}
345-
346-
NumericVector subSection = subseries(ts, i, i + w_size);
347-
std::vector<double>::const_iterator first = series.begin() + i;
348-
std::vector<double>::const_iterator last = series.begin() + i + w_size;
349-
std::vector<double> sub_section(first, last);
350-
351-
// bw << i << "\t[" ;
352-
// for (auto i = subSection.begin(); i != subSection.end(); ++i)
353-
// bw << *i << ',';
354-
// bw << "]\t" ;
355-
356-
sub_section = _znorm(sub_section, n_threshold);
357-
358-
sub_section = _paa(sub_section, paa_size);
359-
// bw << "[" ;
360-
// for (auto i = subSection.begin(); i != subSection.end(); ++i)
361-
// bw << *i << ',';
362-
// bw << "]\t" ;
363-
364-
std::string curr_str = _series_to_string(sub_section, a_size);
365-
// bw << curr_str << "\t" ;
366-
367-
if (!(0 == old_str.length())) {
368-
if ( strategy_exact
369-
&& old_str == curr_str ) {
370-
// std::cout << std::endl;
371-
// bw << "skipped\n" ;
372-
continue;
373-
}
374-
else if (strategy_mindist
375-
&& is_equal_mindist(old_str, curr_str) ) {
376-
// std::cout << std::endl;
377-
continue;
378-
}
379-
}
380-
381-
idx2word.insert(std::make_pair(i, curr_str));
382-
383-
old_str = curr_str;
384-
385-
// std::cout << " " << curr_str << std::endl;
386-
// bw << "kept\n" ;
387-
}
388-
// bw.close();
389-
return idx2word;
315+
std::vector<double> series = Rcpp::as<std::vector<double>>(ts);
316+
std::string nr = Rcpp::as<std::string>(nr_strategy);
317+
std::unordered_map<int, std::string> um = _sax_via_window(
318+
series, w_size, paa_size, a_size, nr, n_threshold);
319+
return std::map<int, std::string>(um.begin(), um.end());
390320
}
391321

392322
//' Discretize a time series with SAX using chunking (no sliding window).

src/znorm.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,33 @@ std::vector<double> _znorm(const std::vector<double>& ts, double threshold) {
5353
return diff;
5454

5555
}
56+
57+
void _znorm_slice(const std::vector<double>& ts, int start, int end,
58+
double threshold, std::vector<double>& out) {
59+
int len = end - start;
60+
out.resize(len);
61+
62+
double sum = 0.0;
63+
for (int i = start; i < end; i++) {
64+
sum += ts[i];
65+
}
66+
double mean = sum / len;
67+
68+
double sq_sum = 0.0;
69+
for (int i = start; i < end; i++) {
70+
double d = ts[i] - mean;
71+
sq_sum += d * d;
72+
}
73+
double stdev = std::sqrt(sq_sum / len);
74+
75+
if (stdev < threshold) {
76+
for (int i = start; i < end; i++) {
77+
out[i - start] = ts[i];
78+
}
79+
return;
80+
}
81+
82+
for (int i = start; i < end; i++) {
83+
out[i - start] = (ts[i] - mean) / stdev;
84+
}
85+
}

0 commit comments

Comments
 (0)