diff options
author | Peter Wu | 2014-07-26 12:31:15 +0200 |
---|---|---|
committer | Pim van den Berg | 2014-08-02 12:29:50 +0200 |
commit | 5d21cdf8e8062f5cc3912c787dce685405a342b3 (patch) | |
tree | 3e2a92e1ff41a7c654070a6d228638460fb42183 /js | |
parent | jsrrdgraph: Remove unused getStringAt, move implementation (diff) | |
download | apt-panopticon_cgp-5d21cdf8e8062f5cc3912c787dce685405a342b3.zip apt-panopticon_cgp-5d21cdf8e8062f5cc3912c787dce685405a342b3.tar.gz apt-panopticon_cgp-5d21cdf8e8062f5cc3912c787dce685405a342b3.tar.bz2 apt-panopticon_cgp-5d21cdf8e8062f5cc3912c787dce685405a342b3.tar.xz |
jsrrdgraph: Use Typed Arrays for better performance
Profiling was done using Firebug in Firefox 31 on Linux x86_64. Test was
performed as follows:
- rrd generated using collectd with default rrdtool settings.
- CPU utilization was visualized with 8 lines (idle, nice, etc.).
- The data has gaps between 20% and 40% and 55% and 85% (were no data
was collected).
- The measured performance was from advancing the data 40% to the right
(there are no gaps before that).
The total running time is 18859.094ms (old), 5151.822ms (new). In the
below profiles, common functions with similar running times and call
counts were omitted. Columns in the profile table: function, call count,
time spent in the function, average time spent per call in *total*
(including other functions called by the function) and percentage of the
total running time. The RrdGraph.data_calc function was not called much,
but seems to be heavy enough to include in the trace.
Profile for old implementation:
- getEndianByteAt 246959 12200.42ms 0.063ms 64.69%
- getDoubleAt 30306 3183.393ms 0.612ms 16.88%
- RRDRRA.getEl 30306 1177.33ms 0.665ms 6.24%
- RRDRRA.calc_idx 30306 449.201ms 0.015ms 2.38%
- (RrdDataFile.build 47 431.986ms total=21189.434ms 2.29%)
- getByteAt 254290 327.369ms 0.013ms 1.74%
- (2 functions omitted)
- getCStringAt 752 103.308ms 0.271ms 0.55%
- RrdRpn.calc 5504 98.55ms 0.018ms 0.52%
- (1 function omitted)
- (RrdGraph.data_calc 1 75.417ms total=174.535ms 0.4%)
- getLongAt 1128 65.796ms 0.212ms 0.35%
Profile for new implementation:
- RRDRRA.getEl 32280 1202.446ms 0.065ms 23.34%
- RrdRpn.calc 43968 848.693ms 0.019ms 16.47%
(probably includes the getByteAt call which is not visible in trace)
- (RrdGraph.data calc 2 616.648ms total=1466.432ms 11.97%)
- getDoubleAt 32280 460.894ms 0.014ms 8.95%
- (RrdDataFile.build 48 438.1ms total=2719.07ms 8.5%)
- (RRDRRA.calc_idx 32280 436.375ms total=436.375ms 8.47%)
- (10 functions omitted)
- getLongAt 1152 16.273ms 0.014ms 0.32%
- (2 functions omitted)
- getCStringAt 768 13.651ms 0.018ms 0.26%
A second test was performed on a graph with no visible data points. I
dragged it to the left to get in the future with only NaN values. Then
the profile started. It turns out that the byte readings are not
dominating here, 99% of the time was spent in RrdRpn.calc and
RrdGraph.data_calc:
- (old) RrdRpn.calc 3.14k 58493s 0.019ms
- (new) RrdRpn.calc 3.10k 56912s 0.018ms
- RrdGraph.data_calc (n=2) took about 46 seconds for both.
- getByteAt (n=12096): 0.014ms (old), invisible/inlined (new)
- getEndianByteAt (n=4608): 0.040ms (old only)
- getCStringAt (n=768): 0.285ms (old), 0.018ms (new)
- getLongAt (n=1152): 0.233ms (old), 0.014ms (new)
Diffstat (limited to '')
-rw-r--r-- | js/binaryXHR.js | 73 |
1 files changed, 66 insertions, 7 deletions
diff --git a/js/binaryXHR.js b/js/binaryXHR.js index 2cd02dd..d18f6a8 100644 --- a/js/binaryXHR.js +++ b/js/binaryXHR.js | |||
@@ -46,6 +46,8 @@ function BinaryFile(data) { | |||
46 | this.getByteAt = function(iOffset) { | 46 | this.getByteAt = function(iOffset) { |
47 | return data.charCodeAt(iOffset) & 0xFF; | 47 | return data.charCodeAt(iOffset) & 0xFF; |
48 | }; | 48 | }; |
49 | } else if (typeof DataView != "undefined" && data instanceof ArrayBuffer) { | ||
50 | dataLength = data.dataLength; | ||
49 | } else if (typeof data === "unknown") { | 51 | } else if (typeof data === "unknown") { |
50 | // Correct. "unknown" as type. MS JScript 8 added this. | 52 | // Correct. "unknown" as type. MS JScript 8 added this. |
51 | dataLength = IEBinary_getLength(data); | 53 | dataLength = IEBinary_getLength(data); |
@@ -61,8 +63,15 @@ function BinaryFile(data) { | |||
61 | return dataLength; | 63 | return dataLength; |
62 | }; | 64 | }; |
63 | 65 | ||
64 | // antique browser, use slower fallback implementation | 66 | if (typeof DataView != "undefined" && data instanceof ArrayBuffer) { |
65 | this.extendWithFallback(data, littleEndian); | 67 | // not an antique browser, use faster TypedArrays |
68 | this.extendWithDataView(data, littleEndian); | ||
69 | // other functions here do not need these | ||
70 | data = null; | ||
71 | } else { | ||
72 | // antique browser, use slower fallback implementation | ||
73 | this.extendWithFallback(data, littleEndian); | ||
74 | } | ||
66 | } | 75 | } |
67 | 76 | ||
68 | BinaryFile.prototype.extendWithFallback = function(data, littleEndian) { | 77 | BinaryFile.prototype.extendWithFallback = function(data, littleEndian) { |
@@ -172,6 +181,42 @@ BinaryFile.prototype.extendWithFallback = function(data, littleEndian) { | |||
172 | }; | 181 | }; |
173 | }; | 182 | }; |
174 | 183 | ||
184 | BinaryFile.prototype.extendWithDataView = function(data, littleEndian) { | ||
185 | "use strict"; | ||
186 | var dv = new DataView(data); | ||
187 | |||
188 | this.getByteAt = dv.getUint8.bind(dv); | ||
189 | this.getSByteAt = dv.getInt8.bind(dv); | ||
190 | this.getShortAt = function(iOffset) { | ||
191 | return dv.getUint16(iOffset, littleEndian); | ||
192 | }; | ||
193 | this.getSShortAt = function(iOffset) { | ||
194 | return dv.getInt16(iOffset, littleEndian); | ||
195 | }; | ||
196 | this.getLongAt = function(iOffset) { | ||
197 | return dv.getUint32(iOffset, littleEndian); | ||
198 | }; | ||
199 | this.getSLongAt = function(iOffset) { | ||
200 | return dv.getInt32(iOffset, littleEndian); | ||
201 | }; | ||
202 | this.getCharAt = function(iOffset) { | ||
203 | return String.fromCharCode(this.getByteAt(iOffset)); | ||
204 | }; | ||
205 | this.getCStringAt = function(iOffset, iMaxLength) { | ||
206 | var str = ""; | ||
207 | do { | ||
208 | var b = this.getByteAt(iOffset++); | ||
209 | if (b === 0) | ||
210 | break; | ||
211 | str += String.fromCharCode(b); | ||
212 | } while (--iMaxLength > 0); | ||
213 | return str; | ||
214 | }; | ||
215 | this.getDoubleAt = function(iOffset) { | ||
216 | return dv.getFloat64(iOffset, littleEndian); | ||
217 | }; | ||
218 | this.getFastDoubleAt = this.getDoubleAt.bind(this); | ||
219 | }; | ||
175 | 220 | ||
176 | 221 | ||
177 | // Use document.write only for stone-age browsers. | 222 | // Use document.write only for stone-age browsers. |
@@ -203,7 +248,7 @@ function FetchBinaryURL(url) { | |||
203 | } | 248 | } |
204 | request.send(null); | 249 | request.send(null); |
205 | 250 | ||
206 | var response=request.responseText; | 251 | var response = request.responseText; |
207 | try { | 252 | try { |
208 | // for older IE versions, the value in responseText is not usable | 253 | // for older IE versions, the value in responseText is not usable |
209 | if (IEBinary_getLength(this.responseBody)>0) { | 254 | if (IEBinary_getLength(this.responseBody)>0) { |
@@ -214,7 +259,18 @@ function FetchBinaryURL(url) { | |||
214 | // not IE, do nothing | 259 | // not IE, do nothing |
215 | } | 260 | } |
216 | 261 | ||
217 | var bf=new BinaryFile(response); | 262 | // cannot use responseType == "arraybuffer" for synchronous requests, so |
263 | // convert it afterwards | ||
264 | if (typeof ArrayBuffer != "undefined") { | ||
265 | var buffer = new ArrayBuffer(response.length); | ||
266 | var bv = new Uint8Array(buffer); | ||
267 | for (var i = 0; i < response.length; i++) { | ||
268 | bv[i] = response.charCodeAt(i); | ||
269 | } | ||
270 | response = buffer; | ||
271 | } | ||
272 | |||
273 | var bf = new BinaryFile(response); | ||
218 | return bf; | 274 | return bf; |
219 | } | 275 | } |
220 | 276 | ||
@@ -229,7 +285,8 @@ function FetchBinaryURLAsync(url, callback, callback_arg) { | |||
229 | "use strict"; | 285 | "use strict"; |
230 | var callback_wrapper = function() { | 286 | var callback_wrapper = function() { |
231 | if(this.readyState === 4) { | 287 | if(this.readyState === 4) { |
232 | var response=this.responseText; | 288 | // ArrayBuffer response or just the response as string |
289 | var response = this.response || this.responseText; | ||
233 | try { | 290 | try { |
234 | // for older IE versions, the value in responseText is not usable | 291 | // for older IE versions, the value in responseText is not usable |
235 | if (IEBinary_getLength(this.responseBody)>0) { | 292 | if (IEBinary_getLength(this.responseBody)>0) { |
@@ -240,7 +297,7 @@ function FetchBinaryURLAsync(url, callback, callback_arg) { | |||
240 | // not IE, do nothing | 297 | // not IE, do nothing |
241 | } | 298 | } |
242 | 299 | ||
243 | var bf=new BinaryFile(response); | 300 | var bf = new BinaryFile(response); |
244 | if (callback_arg) { | 301 | if (callback_arg) { |
245 | callback(bf, callback_arg); | 302 | callback(bf, callback_arg); |
246 | } else { | 303 | } else { |
@@ -251,7 +308,9 @@ function FetchBinaryURLAsync(url, callback, callback_arg) { | |||
251 | 308 | ||
252 | var request = new XMLHttpRequest(); | 309 | var request = new XMLHttpRequest(); |
253 | request.onreadystatechange = callback_wrapper; | 310 | request.onreadystatechange = callback_wrapper; |
254 | request.open("GET", url,true); | 311 | request.open("GET", url, true); |
312 | // Supported since Chrome 10, FF 6, IE 10, Opera 11.60 (source: MDN) | ||
313 | request.responseType = "arraybuffer"; | ||
255 | try { | 314 | try { |
256 | request.overrideMimeType('text/plain; charset=x-user-defined'); | 315 | request.overrideMimeType('text/plain; charset=x-user-defined'); |
257 | } catch (err) { | 316 | } catch (err) { |