[NB] Trying to implement a ReadStream on NBExternalAddress
Hello, I'm trying to implement a binary ReadStream that gets its data not from a Smalltalk collection but from some external memory address. I got this address from a library call using NativeBoost. The NBExternalAddress has methods to access bytes, int's and longs but there is no method to get a ByteArray starting at a specified offset and with a specified length. I need a method like this to implement the ReadStream>>next: method efficiently. Is it possible ti implement such a method or are there other ways to implement a ReadStream efficiently? In [1] I read that there was already an idea to implement a nbAddressAt: method. This would also be using in combination with NativeBoost class>>#memCopy:to:size Regrads, Jan. [1] http://forum.world.st/Pharo-dev-NB-Review-amp-fixes-amp-ideas-td4698514.html
Hi Jan, I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project. Concerning the Streams, I think it is the easiest solution to wrap around a ByteArray. Otherwise you could subclass one of the main stream classes and implement your own primitive methods with FFI to read and write bytes. In the worst case you will have to implement all primitive methods with FFI that you find in the StandardFileStream. On 2013-09-22, at 10:12, Jan van de Sandt <jvdsandt@gmail.com> wrote:
Hello,
I'm trying to implement a binary ReadStream that gets its data not from a Smalltalk collection but from some external memory address. I got this address from a library call using NativeBoost.
The NBExternalAddress has methods to access bytes, int's and longs but there is no method to get a ByteArray starting at a specified offset and with a specified length. I need a method like this to implement the ReadStream>>next: method efficiently.
Is it possible ti implement such a method or are there other ways to implement a ReadStream efficiently?
In [1] I read that there was already an idea to implement a nbAddressAt: method. This would also be using in combination with NativeBoost class>>#memCopy:to:size
Regrads, Jan.
[1] http://forum.world.st/Pharo-dev-NB-Review-amp-fixes-amp-ideas-td4698514.html
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com> wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size. same way, writing, just swap the source and destination: newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size. but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.: copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress this where we need to introduce special 'field address' type, so you can construct it like this: offsetAddress := buffer nbAddressAt: offset. so then you can use it to pass to any function, which expects address, like memory copy or any foreign function. Since objects are moving in memory, we cannot calculate address of field before hand: address := NBExternalAddress value: someObject address + offset. because if GC will happen, after computing such address and its actual use, you will read/write to wrong location. Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Concerning the Streams, I think it is the easiest solution to wrap around a ByteArray. Otherwise you could subclass one of the main stream classes and implement your own primitive methods with FFI to read and write bytes. In the worst case you will have to implement all primitive methods with FFI that you find in the StandardFileStream.
On 2013-09-22, at 10:12, Jan van de Sandt <jvdsandt@gmail.com> wrote:
Hello,
I'm trying to implement a binary ReadStream that gets its data not from a Smalltalk collection but from some external memory address. I got this address from a library call using NativeBoost.
The NBExternalAddress has methods to access bytes, int's and longs but there is no method to get a ByteArray starting at a specified offset and with a specified length. I need a method like this to implement the ReadStream>>next: method efficiently.
Is it possible ti implement such a method or are there other ways to implement a ReadStream efficiently?
In [1] I read that there was already an idea to implement a nbAddressAt: method. This would also be using in combination with NativeBoost class>>#memCopy:to:size
Regrads, Jan.
[1]
http://forum.world.st/Pharo-dev-NB-Review-amp-fixes-amp-ideas-td4698514.html
-- Best regards, Igor Stasenko.
On 23 September 2013 21:40, Igor Stasenko <siguctua@gmail.com> wrote:
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com> wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read
newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size.
same way, writing, just swap the source and destination:
newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size.
but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.:
copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress
this where we need to introduce special 'field address' type, so you can construct it like this:
offsetAddress := buffer nbAddressAt: offset.
so then you can use it to pass to any function, which expects address, like memory copy or any foreign function.
Since objects are moving in memory, we cannot calculate address of field before hand:
address := NBExternalAddress value: someObject address + offset.
because if GC will happen, after computing such address and its actual use, you will read/write to wrong location.
** after computing and *before* actual use **
Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Things would be much simpler if we could have pinning, isnt? :)
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
Isn't Eliot just implementing this feature, having a segment of non relocatable objects? 2013/9/23 Igor Stasenko <siguctua@gmail.com>
On 23 September 2013 21:40, Igor Stasenko <siguctua@gmail.com> wrote:
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com> wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read
newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size.
same way, writing, just swap the source and destination:
newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size.
but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.:
copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress
this where we need to introduce special 'field address' type, so you can construct it like this:
offsetAddress := buffer nbAddressAt: offset.
so then you can use it to pass to any function, which expects address, like memory copy or any foreign function.
Since objects are moving in memory, we cannot calculate address of field before hand:
address := NBExternalAddress value: someObject address + offset.
because if GC will happen, after computing such address and its actual use, you will read/write to wrong location.
** after computing and *before* actual use **
Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Things would be much simpler if we could have pinning, isnt? :)
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
2013/9/24 Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com>
Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
AFAIU, yes ;-) Cheers, Luc
2013/9/23 Igor Stasenko <siguctua@gmail.com>
On 23 September 2013 21:40, Igor Stasenko <siguctua@gmail.com> wrote:
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com>wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read
newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size.
same way, writing, just swap the source and destination:
newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size.
but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.:
copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress
this where we need to introduce special 'field address' type, so you can construct it like this:
offsetAddress := buffer nbAddressAt: offset.
so then you can use it to pass to any function, which expects address, like memory copy or any foreign function.
Since objects are moving in memory, we cannot calculate address of field before hand:
address := NBExternalAddress value: someObject address + offset.
because if GC will happen, after computing such address and its actual use, you will read/write to wrong location.
** after computing and *before* actual use **
Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Things would be much simpler if we could have pinning, isnt? :)
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On Mon, Sep 23, 2013 at 3:10 PM, Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com> wrote:
Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
Sort of. Spur will allow any object in old space to stay still, and through become can move any object to old space very simply. So instead of having a special fixed space segment it has a best-bit compaction algorithm that doesn't slide objects, but moves them into holes. With this kind of compaction it is very easy to leave objects put. A further advantage of Spur is that objects have 64-bit alignment so passing arrays to e.g. code using sse instructions, won't cause potential alignment faults. But for NB see below on the hack that the ThreadedFFI uses.
2013/9/23 Igor Stasenko <siguctua@gmail.com>
On 23 September 2013 21:40, Igor Stasenko <siguctua@gmail.com> wrote:
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com>wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read
newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size.
same way, writing, just swap the source and destination:
newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size.
but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.:
copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress
this where we need to introduce special 'field address' type, so you can construct it like this:
offsetAddress := buffer nbAddressAt: offset.
so then you can use it to pass to any function, which expects address, like memory copy or any foreign function.
Since objects are moving in memory, we cannot calculate address of field before hand:
address := NBExternalAddress value: someObject address + offset.
because if GC will happen, after computing such address and its actual use, you will read/write to wrong location.
** after computing and *before* actual use **
Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Things would be much simpler if we could have pinning, isnt? :)
Yes, but for the moment there is a hack one can use, a neat hack invented by Andreas Raab. The Squeak GC is a two-space GC, old space (collected by fullGC) and new space (collected by incrementalGC). An incrementalGC will move objects in new space but leave objects on old space alone. A tenuringIncrementalGC will compact new space and then make new space part of old space. Therefore one way of nearly pinning objects is to do a young GC to tenure objects into old space via tenuringIncrementalGC and then lock fullGC, prevent fullGC from running, until the external call is finished. All arguments to the call become old, and they won't be moved until the fuuGCLock is released. This doesn't help passing a buffer that will be used after the call returns, but it does help a buffer being passed to code that might callback. See uses of PrimErrObjectMayMove, e.g. ThreadedFFIPlugin>>primitiveCallout and platforms/Cross/plugins/FilePlugin/sqFilePluginBasicPrims.c>>sqFileReadIntoAt HTH eliot
On Sep 24, 2013, at 9:58 , Eliot Miranda <eliot.miranda@gmail.com> wrote:
On Mon, Sep 23, 2013 at 3:10 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote: Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
Sort of. Spur will allow any object in old space to stay still, and through become can move any object to old space very simply. So instead of having a special fixed space segment it has a best-bit compaction algorithm that doesn't slide objects, but moves them into holes. With this kind of compaction it is very easy to leave objects put.
A further advantage of Spur is that objects have 64-bit alignment so passing arrays to e.g. code using sse instructions, won't cause potential alignment faults.
Not to be a drag, but SSE operations have 16-byte alignment requirement. With 8-byte aligmnent of first indexable slot, you'd still need to use aligned loads/stores. Cheers, Henry
Hi Henry, On Wed, Sep 25, 2013 at 1:14 AM, Henrik Johansen < henrik.s.johansen@veloxit.no> wrote:
On Sep 24, 2013, at 9:58 , Eliot Miranda <eliot.miranda@gmail.com> wrote:
On Mon, Sep 23, 2013 at 3:10 PM, Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com> wrote:
Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
Sort of. Spur will allow any object in old space to stay still, and through become can move any object to old space very simply. So instead of having a special fixed space segment it has a best-bit compaction algorithm that doesn't slide objects, but moves them into holes. With this kind of compaction it is very easy to leave objects put.
A further advantage of Spur is that objects have 64-bit alignment so passing arrays to e.g. code using sse instructions, won't cause potential alignment faults.
Not to be a drag, but SSE operations have 16-byte alignment requirement. With 8-byte aligmnent of first indexable slot, you'd still need to use aligned loads/stores.
Talking out-of-line with someone who has lots of experience with Smalltalk memory management it came to me that the pinning primitive could (and should) take an aligmnet argument). So one says e.g. buffer := (ByteArray new: 1024) pinWithByteAlignment: 16 -- best, Eliot
On Sep 25, 2013, at 9:36 , Eliot Miranda <eliot.miranda@gmail.com> wrote:
Hi Henry,
On Wed, Sep 25, 2013 at 1:14 AM, Henrik Johansen <henrik.s.johansen@veloxit.no> wrote:
On Sep 24, 2013, at 9:58 , Eliot Miranda <eliot.miranda@gmail.com> wrote:
On Mon, Sep 23, 2013 at 3:10 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote: Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
Sort of. Spur will allow any object in old space to stay still, and through become can move any object to old space very simply. So instead of having a special fixed space segment it has a best-bit compaction algorithm that doesn't slide objects, but moves them into holes. With this kind of compaction it is very easy to leave objects put.
A further advantage of Spur is that objects have 64-bit alignment so passing arrays to e.g. code using sse instructions, won't cause potential alignment faults.
Not to be a drag, but SSE operations have 16-byte alignment requirement. With 8-byte aligmnent of first indexable slot, you'd still need to use aligned loads/stores.
Talking out-of-line with someone who has lots of experience with Smalltalk memory management it came to me that the pinning primitive could (and should) take an aligmnet argument). So one says e.g.
buffer := (ByteArray new: 1024) pinWithByteAlignment: 16
-- best, Eliot
I feel this, plus an FFI that lets you specify parameter alignment tags*, and pin the arguments accordingly, would be an excellent solution. For example, AVX ops already requires 32-byte alignment. Covering all the cases where alignment might be an issue seems like a hefty task for the allocator to care about by default. Might be a good idea to make sure they're padded to a corresponding multiple of bytes as well, just to be safe :) (Say, some library using movapd to store the results back as well, without regarding odd-numbers of elements, thus potentially overwriting the next objects header) Cheers, Henry P.S. For the record, alignment isn't ever an issue when writing your own SSE functions using NB. You'd simply \\16/32 the provided pointer, do aligned loads, and mask out the leading/trailing bytes when storing using maskmovdqu/vmaskmov** ;) *A custom FFI interrupt handler for #GP returning an error instead of crashing when an alignment tag is missing would be out of the question though, right? ;) ** Well, if someone gets around to implementing the VEX-encoded instructions in NB, that is
On 24 September 2013 21:58, Eliot Miranda <eliot.miranda@gmail.com> wrote:
On Mon, Sep 23, 2013 at 3:10 PM, Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com> wrote:
Isn't Eliot just implementing this feature, having a segment of non relocatable objects?
Sort of. Spur will allow any object in old space to stay still, and through become can move any object to old space very simply. So instead of having a special fixed space segment it has a best-bit compaction algorithm that doesn't slide objects, but moves them into holes. With this kind of compaction it is very easy to leave objects put.
A further advantage of Spur is that objects have 64-bit alignment so passing arrays to e.g. code using sse instructions, won't cause potential alignment faults.
But for NB see below on the hack that the ThreadedFFI uses.
2013/9/23 Igor Stasenko <siguctua@gmail.com>
On 23 September 2013 21:40, Igor Stasenko <siguctua@gmail.com> wrote:
On 23 September 2013 16:23, Camillo Bruni <camillobruni@gmail.com>wrote:
Hi Jan,
I think I will add the ByteArray accessor to NBExternalAddress today or tomorrow since I need it as well for another project.
hmm, reading from memory into bytearray can be done with memory copy:
inputs: address , offset , size to read
newAddress := NBExternalAddress value: address value + offset. buffer := ByteArray new: size. NativeBoost memCopy: newAddress to: buffer size: size.
same way, writing, just swap the source and destination:
newAddress := NBExternalAddress value: address value + offset. buffer "is given from somewhere". NativeBoost memCopy: buffer to: newAddress size: size.
but as Jan noted, you cannot tell to write starting at specified offset from/to bytearray, e.g.:
copy from: address to: buffer + someOffset neither: copy from: buffer + someOffset to: someAddress
this where we need to introduce special 'field address' type, so you can construct it like this:
offsetAddress := buffer nbAddressAt: offset.
so then you can use it to pass to any function, which expects address, like memory copy or any foreign function.
Since objects are moving in memory, we cannot calculate address of field before hand:
address := NBExternalAddress value: someObject address + offset.
because if GC will happen, after computing such address and its actual use, you will read/write to wrong location.
** after computing and *before* actual use **
Thus we should keep oop + offset up to the point of passing it to external function, under controllable conditions, that guarantee there's no GC is possible.
Things would be much simpler if we could have pinning, isnt? :)
Yes, but for the moment there is a hack one can use, a neat hack invented by Andreas Raab. The Squeak GC is a two-space GC, old space (collected by fullGC) and new space (collected by incrementalGC). An incrementalGC will move objects in new space but leave objects on old space alone. A tenuringIncrementalGC will compact new space and then make new space part of old space. Therefore one way of nearly pinning objects is to do a young GC to tenure objects into old space via tenuringIncrementalGC and then lock fullGC, prevent fullGC from running, until the external call is finished. All arguments to the call become old, and they won't be moved until the fuuGCLock is released. This doesn't help passing a buffer that will be used after the call returns, but it does help a buffer being passed to code that might callback.
This recipe feels like taking roots from voodoo cult :) and quite unreliable and limiting: - to ensure more or less 'stable' system behavior, you need to do fullGC before every such call - where is guarantee that user-code which implements callback won't produce enough garbage to force full GC? I'd prefer having strong public contract with VM that allows me to pin object(s) in memory, instead of relying on private details of VM's internals or, what is worse, on user's sanity :) Besides, one thing i found lately: its a pity that our object format don't using butterfly layout.. i realized that looking at some code, which tries pass an array of pointers.. and doing a lot of manual conversions for that.. while if we could have a butterfly headers, we could just pass an object pointer (like Array instance) as C array of pointers.
See uses of PrimErrObjectMayMove, e.g. ThreadedFFIPlugin>>primitiveCallout and platforms/Cross/plugins/FilePlugin/sqFilePluginBasicPrims.c>>sqFileReadIntoAt
HTH eliot
-- Best regards, Igor Stasenko.
if we started about ideas.. what about good-old indirection? :) we could use extra object format with indirection to its data, with following layout: <header bitOr: PINNED><data pointer> so, the data (only for variable bytes, not var-references, of course) associated with such object can be located in separate non-movable region and you need to do extra work when scavenging objects with such special format, but for the rest, it almost the same: - the object (oop) itself can still move in memory, no big deal since its data pointer remains the same extending the idea, we could have even objects which pointing inside of an object.. like with Nicolas'es unboxed complex numbers, i could say: i want an object which points to imaginary field of unboxed complex value. so the complex value can be represented as a pinned object big enough to hold 2 floats: <header bitOr: PINNED><data pointer to 2 64-bit floats> and the object representing an imaginary float part can be following: <header bitOr: PINNED-SLAVE><complex oop><data pointer + 8> the main difference between PINNED and PINNED-SLAVE, that during scavenging, GC will ignore and won't deallocate the buffer pointed to by the slave, that's why it holds a reference to its master (<complex oop> in the example) to prevent deallocation/garbage collection of master and allocated buffer. in fact, PINNED-SLAVE format can be used for pointing in memory to anything (even outside memory managed by GC, because it never (de)allocates that memory), like that i can easily represent some data blob/buffer provided by external library as ByteArray object in image: <header bitOr: PINNED-SLAVE><nil><buffer pointer> -- Best regards, Igor Stasenko.
participants (7)
-
Camillo Bruni -
Eliot Miranda -
Henrik Johansen -
Igor Stasenko -
Jan van de Sandt -
Luc Fabresse -
Nicolas Cellier