Skip to content

Commit f3bcddc

Browse files
committed
Use TypedArray to achieve good performance
1 parent c2577cc commit f3bcddc

File tree

9 files changed

+816
-86
lines changed

9 files changed

+816
-86
lines changed

lib/message_translator.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ function toPlainObject(message) {
120120
if (!message) return undefined;
121121

122122
// TODO(Kenny): make sure `message` is a ROS message
123-
// TODO(Kenny): deal with primitive-type array (convert to TypedArray)
124123

125124
if (message.isROSArray) {
126125
// It's a ROS message array
@@ -139,6 +138,8 @@ function toPlainObject(message) {
139138
const name = def.fields[i].name;
140139
if (def.fields[i].type.isPrimitiveType) {
141140
// Direct assignment
141+
// Note: TypedArray also falls into this branch
142+
// TODO(Kenny): make sure Int64 & Uint64 type can be copied here
142143
obj[name] = message[name];
143144
} else {
144145
// Proceed further

rosidl_gen/deallocator.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
const rclnodejs = require('bindings')('rclnodejs');
1818

1919
let deallocator = {
20+
delayFreeStructMember(refObj, type, name) {
21+
return rclnodejs.createArrayBufferCleaner(refObj.ref(), type.fields[name].offset);
22+
},
2023
freeStructMember(refObj, type, name) {
2124
rclnodejs.freeMemeoryAtOffset(refObj.ref(), type.fields[name].offset);
2225
}

rosidl_gen/templates/message.dot

Lines changed: 119 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@
55

66
'use strict';
77

8-
const StructType = require('ref-struct');
9-
const ref = require('ref');
10-
const ArrayType = require('ref-array');
11-
const primitiveTypes = require('../../rosidl_gen/generator_primitive.js');
12-
const deallocator = require('../../rosidl_gen/deallocator.js');
13-
148
{{
159
let objectWrapper = it.spec.msgName + 'Wrapper';
1610
let arrayWrapper = it.spec.msgName + 'ArrayWrapper';
@@ -73,39 +67,63 @@ function getPrimitiveNameByType(type) {
7367
}
7468

7569
function getTypedArrayName(type) {
76-
if (type.type === 'int8') {
70+
const t = type.type.toLowerCase();
71+
if (t === 'int8') {
7772
return 'Int8Array';
78-
} else if (type.type === 'uint8') {
73+
} else if (t === 'uint8') {
7974
return 'Uint8Array';
80-
} else if (type.type === 'int16') {
75+
} else if (t === 'int16') {
8176
return 'Int16Array';
82-
} else if (type.type === 'uint16') {
77+
} else if (t === 'uint16') {
8378
return 'Uint16Array';
84-
} else if (type.type === 'int32') {
79+
} else if (t === 'int32') {
8580
return 'Int32Array';
86-
} else if (type.type === 'uint32') {
81+
} else if (t === 'uint32') {
8782
return 'Uint32Array';
88-
} else if (type.type === 'int64') {
89-
return 'Int64Array';
90-
} else if (type.type === 'uint64') {
91-
return 'Uint64Array';
92-
} else if (type.type === 'float64') {
83+
} else if (t === 'float64') {
9384
return 'Float64Array';
94-
} else if (type.type === 'float32') {
85+
} else if (t === 'float32') {
9586
return 'Float32Array';
96-
} else if (type.type === 'char') {
87+
} else if (t === 'char') {
9788
return 'Int8Array';
98-
} else if (type.type === 'byte') {
89+
} else if (t === 'byte') {
9990
return 'Uint8Array';
10091
} else {
10192
return '';
10293
}
10394
}
10495

96+
function getTypedArrayElementName(type) {
97+
const t = type.type.toLowerCase();
98+
if (t === 'int8') {
99+
return 'int8';
100+
} else if (t === 'uint8') {
101+
return 'uint8';
102+
} else if (t === 'int16') {
103+
return 'int16';
104+
} else if (t === 'uint16') {
105+
return 'uint16';
106+
} else if (t === 'int32') {
107+
return 'int32';
108+
} else if (t === 'uint32') {
109+
return 'uint32';
110+
} else if (t === 'float64') {
111+
return 'double';
112+
} else if (t === 'float32') {
113+
return 'float';
114+
} else if (t === 'char') {
115+
return 'int8';
116+
} else if (t === 'byte') {
117+
return 'uint8';
118+
} else {
119+
return '';
120+
}
121+
}
122+
105123
const primitiveBaseType = ['Bool', 'Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32',
106124
'Int64', 'UInt64', 'Float64', 'Float32', 'Char', 'Byte', 'String'];
107125
const typedArrayType = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32',
108-
'int64', 'uint64', 'float64', 'float32', 'char', 'byte'];
126+
'float64', 'float32', 'char', 'byte'];
109127

110128
let existedModules = [];
111129

@@ -118,9 +136,13 @@ function isPrimitivePackage(baseType) {
118136
}
119137

120138
function isTypedArrayType(type) {
121-
return typedArrayType.indexOf(type.type) !== -1;
139+
return typedArrayType.indexOf(type.type.toLowerCase()) !== -1;
122140
}
123141

142+
const usePlainTypedArray = isTypedArrayType(it.spec.baseType);
143+
const currentTypedArray = getTypedArrayName(it.spec.baseType);
144+
const currentTypedArrayElementType = getTypedArrayElementName(it.spec.baseType);
145+
124146
function shouldRequire(baseType, fieldType) {
125147
let requiredModule = '{{=getPackageNameByType(fieldType)}}/{{=getJSFileNameByType(fieldType)}}';
126148
let shouldRequire = !isPrimitivePackage(baseType) && (fieldType.isArray || !fieldType.isPrimitiveType || fieldType.type === 'string');
@@ -158,6 +180,15 @@ function getPackageNameByType(type) {
158180
}
159181
}}
160182

183+
{{? usePlainTypedArray}}
184+
const rclnodejs = require('bindings')('rclnodejs');
185+
{{?}}
186+
const StructType = require('ref-struct');
187+
const ref = require('ref');
188+
const ArrayType = require('ref-array');
189+
const primitiveTypes = require('../../rosidl_gen/generator_primitive.js');
190+
const deallocator = require('../../rosidl_gen/deallocator.js');
191+
161192
{{~ it.spec.fields :field}}
162193
{{? shouldRequire(it.spec.baseType, field.type)}}
163194
let {{=getWrapperNameByType(field.type)}} = require('../../generated/{{=getPackageNameByType(field.type)}}/{{=getJSFileNameByType(field.type)}}');
@@ -182,7 +213,11 @@ const {{=refObjectType}} = StructType({
182213

183214
const {{=refArrayType}} = ArrayType({{=refObjectType}});
184215
const {{=refObjectArrayType}} = StructType({
185-
data: ArrayType({{=refObjectType}}),
216+
{{? usePlainTypedArray }}
217+
data: ref.refType(ref.types.{{=currentTypedArrayElementType}}),
218+
{{?? true}}
219+
data: {{=refArrayType}},
220+
{{?}}
186221
size: ref.types.size_t,
187222
capacity: ref.types.size_t,
188223
});
@@ -283,7 +318,11 @@ class {{=objectWrapper}} {
283318
{{? field.type.isArray}}
284319
if (refObject.{{=field.name}}.size != 0) {
285320
{{=getWrapperNameByType(field.type)}}.ArrayType.freeArray(refObject.{{=field.name}});
286-
deallocator.freeStructMember(refObject.{{=field.name}}, {{=getWrapperNameByType(field.type)}}.refObjectArrayType, 'data');
321+
if ({{=getWrapperNameByType(field.type)}}.ArrayType.useTypedArray) {
322+
deallocator.delayFreeStructMember(refObject.{{=field.name}}, {{=getWrapperNameByType(field.type)}}.refObjectArrayType, 'data');
323+
} else {
324+
deallocator.freeStructMember(refObject.{{=field.name}}, {{=getWrapperNameByType(field.type)}}.refObjectArrayType, 'data');
325+
}
287326
}
288327
{{?? !field.type.isPrimitiveType || (field.type.type === 'string' && it.spec.msgName !== 'String')}}
289328
{{=getWrapperNameByType(field.type)}}.freeStruct(refObject.{{=field.name}});
@@ -312,12 +351,7 @@ class {{=objectWrapper}} {
312351
{{~ it.spec.fields :field}}
313352
get {{=field.name}}() {
314353
{{? field.type.isArray && isTypedArrayType(field.type)}}
315-
const src = this._wrapperFields['{{=field.name}}'].data;
316-
let values = new {{=getTypedArrayName(field.type)}}(src.length);
317-
for (let i = 0; i < src.length; ++i) {
318-
values[i] = src[i].data;
319-
}
320-
return values;
354+
return this._wrapperFields['{{=field.name}}'].data;
321355
{{?? field.type.isArray && field.type.isPrimitiveType}}
322356
let values = [];
323357
this._wrapperFields['{{=field.name}}'].data.forEach((wrapper, index) => {
@@ -394,30 +428,49 @@ class {{=arrayWrapper}} {
394428
}
395429

396430
fill(values) {
431+
{{? usePlainTypedArray }}
432+
if (Array.isArray(values)) {
433+
// Convert JavaScript array
434+
this._wrappers = new {{=currentTypedArray}}(values);
435+
} else {
436+
this._wrappers = values;
437+
}
438+
{{?? isPrimitivePackage(it.spec.baseType) }}
439+
// Now only for string/bool array
397440
const length = values.length;
398441
this._resize(length);
399-
{{? isPrimitivePackage(it.spec.baseType)}}
400-
// Use for loop to make it also work for TypedArray
401442
for (let i = 0; i < length; ++i) {
402443
let wrapper = new {{=objectWrapper}}();
403444
wrapper.data = values[i];
404445
this._wrappers[i] = wrapper;
405446
}
406-
{{?? !isPrimitivePackage(it.spec.baseType)}}
447+
{{?? true}}
448+
const length = values.length;
449+
this._resize(length);
407450
values.forEach((value, index) => {
408451
this._wrappers[index].copy(value);
409452
});
410453
{{?}}
411454
}
412455

456+
// Put all data currently stored in `this._wrappers` into `this._refObject`
413457
freeze(own) {
458+
{{? usePlainTypedArray }}
459+
// When it's a TypedArray: no need to copy to `this._refArray`
460+
{{?? true}}
414461
this._wrappers.forEach((wrapper, index) => {
415462
wrapper.freeze(own);
416463
this._refArray[index] = wrapper.refObject;
417464
});
465+
{{?}}
418466
this._refObject.size = this._wrappers.length;
419467
this._refObject.capacity = this._wrappers.length;
468+
{{? usePlainTypedArray }}
469+
const buffer = Buffer.from(new Uint8Array(this._wrappers.buffer));
470+
this._refObject.data = buffer;
471+
{{?? true}}
420472
this._refObject.data = this._refArray.buffer;
473+
{{?}}
421474
}
422475

423476
get refObject() {
@@ -460,26 +513,43 @@ class {{=arrayWrapper}} {
460513
throw new RangeError('Invalid argument: should provide a positive number');
461514
return;
462515
}
516+
{{? usePlainTypedArray }}
517+
this._refArray = undefined;
518+
{{?? true }}
463519
this._refArray = new {{=refArrayType}}(size);
520+
{{?}}
464521
this._refObject = new {{=refObjectArrayType}}();
465522
this._refObject.size = size;
466523
this._refObject.capacity = size;
467524

525+
{{? usePlainTypedArray }}
526+
this._wrappers = new {{=currentTypedArray}}(size);
527+
{{?? true }}
468528
this._wrappers = new Array();
469529
for (let i = 0; i < size; i++) {
470530
this._wrappers.push(new {{=objectWrapper}}());
471531
}
532+
{{?}}
472533
}
473534

535+
// Copy all data from `this._refObject` into `this._wrappers`
474536
copyRefObject(refObject) {
475537
this._refObject = refObject;
538+
539+
{{? usePlainTypedArray }}
540+
const byteLen = refObject.size * ref.types.{{=currentTypedArrayElementType}}.size;
541+
// An ArrayBuffer object that doesn't hold the ownership of the address
542+
const arrayBuffer = rclnodejs.createArrayBufferFromAddress(refObject.data, byteLen);
543+
this._wrappers = new {{=currentTypedArray}}(arrayBuffer);
544+
{{?? true }}
476545
let refObjectArray = this._refObject.data;
477546
refObjectArray.length = this._refObject.size;
478547
this._resize(this._refObject.size);
479548

480549
for (let index = 0; index < this._refObject.size; index++) {
481550
this._wrappers[index].copyRefObject(refObjectArray[index]);
482551
}
552+
{{?}}
483553
}
484554

485555
copy(other) {
@@ -488,18 +558,26 @@ class {{=arrayWrapper}} {
488558
}
489559

490560
this._resize(other.size);
561+
{{? usePlainTypedArray }}
562+
this._wrappers = other._wrappers.slice();
563+
{{?? true }}
491564
// Array deep copy
492565
other._wrappers.forEach((wrapper, index) => {
493566
this._wrappers[index].copy(wrapper);
494567
});
568+
{{?}}
495569
}
496570

497571
static freeArray(refObject) {
572+
{{? usePlainTypedArray }}
573+
// For TypedArray: .data will be 'free()'-ed in parent struct
574+
{{?? true }}
498575
let refObjectArray = refObject.data;
499576
refObjectArray.length = refObject.size;
500577
for (let index = 0; index < refObject.size; index++) {
501578
{{=objectWrapper}}.freeStruct(refObjectArray[index]);
502579
}
580+
{{?}}
503581
}
504582

505583
static get elementType() {
@@ -510,6 +588,14 @@ class {{=arrayWrapper}} {
510588
return true;
511589
}
512590

591+
static get isROSArray() {
592+
return true;
593+
}
594+
595+
static get useTypedArray() {
596+
return {{=usePlainTypedArray}};
597+
}
598+
513599
get classType() {
514600
return {{=arrayWrapper}};
515601
}

src/rcl_bindings.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,10 @@ NAN_METHOD(InitString) {
696696
info.GetReturnValue().Set(Nan::Undefined());
697697
}
698698

699+
inline char* GetBufAddr(v8::Local<v8::Value> buf) {
700+
return node::Buffer::Data(buf.As<v8::Object>());
701+
}
702+
699703
NAN_METHOD(FreeMemeoryAtOffset) {
700704
v8::Local<v8::Value> buf = info[0];
701705
if (!node::Buffer::HasInstance(buf)) {
@@ -704,7 +708,7 @@ NAN_METHOD(FreeMemeoryAtOffset) {
704708

705709
int64_t offset =
706710
info[1]->IsNumber() ? Nan::To<int64_t>(info[1]).FromJust() : 0;
707-
char* ptr = node::Buffer::Data(buf.As<v8::Object>()) + offset;
711+
auto ptr = GetBufAddr(buf) + offset;
708712

709713
if (ptr == nullptr) {
710714
return Nan::ThrowError("Cannot read from NULL pointer");
@@ -715,6 +719,30 @@ NAN_METHOD(FreeMemeoryAtOffset) {
715719
info.GetReturnValue().Set(Nan::Undefined());
716720
}
717721

722+
NAN_METHOD(CreateArrayBufferFromAddress) {
723+
auto address = GetBufAddr(info[0]);
724+
int32_t length = Nan::To<int32_t>(info[1]).FromJust();
725+
726+
auto array_buffer = v8::ArrayBuffer::New(
727+
v8::Isolate::GetCurrent(), address, length,
728+
v8::ArrayBufferCreationMode::kExternalized);
729+
730+
info.GetReturnValue().Set(array_buffer);
731+
}
732+
733+
NAN_METHOD(CreateArrayBufferCleaner) {
734+
auto address = GetBufAddr(info[0]);
735+
int32_t offset = Nan::To<int32_t>(info[1]).FromJust();
736+
737+
char* target = *reinterpret_cast<char**>(address + offset);
738+
info.GetReturnValue().Set(RclHandle::NewInstance(
739+
target,
740+
nullptr,
741+
[] {
742+
return RCL_RET_OK;
743+
}));
744+
}
745+
718746
uint32_t GetBindingMethodsCount(BindingMethod* methods) {
719747
uint32_t count = 0;
720748
while (methods[count].function) {
@@ -754,6 +782,8 @@ BindingMethod binding_methods[] = {
754782
{"getNamespace", GetNamespace},
755783
{"initString", InitString},
756784
{"freeMemeoryAtOffset", FreeMemeoryAtOffset},
785+
{"createArrayBufferFromAddress", CreateArrayBufferFromAddress},
786+
{"createArrayBufferCleaner", CreateArrayBufferCleaner},
757787
{"", nullptr}};
758788

759789
} // namespace rclnodejs

0 commit comments

Comments
 (0)