stream interfaces - with ranges

Artur Skawina art.08.09 at gmail.com
Fri May 18 10:30:32 PDT 2012


On 05/18/12 17:43, kenji hara wrote:
> 2012/5/19 Artur Skawina <art.08.09 at gmail.com>:
>> On 05/18/12 15:51, kenji hara wrote:
>>> OK. If reading bytes from underlying device failed, your 'fronts' can
>>> return empty slice. I understood.
>>> But, It is still *not efficient*. The returned slice will specifies a
>>> buffer controlled by underlying device. If you want to gather bytes
>>> into one chunk, you must copy bytes from returned slice to your chunk.
>>> We should reduce copying memories as much as possible.
>>
>> Depends if your input range supports zero-copy or not. IOW you avoid
>> the copy iff the range can somehow write the data directly to the caller
>> provided buffer. This can be true eg for file reads, where you can tell
>> the read(2) syscall to write into the user buffer. But what if you need to
>> buffer the stream? An intermediate buffer can become necessary anyway.
>> But, as i said before, i agree that a caller-provided-buffer-interface
>> is useful.
>>
>>   E[] fronts();
>>   void fronts(ref E[]);
>>
>> And one can be implemented in terms of the other, ie:
>>
>>  E[] fronts[] { E[] els; fronts(els); return els; }
>>  void fronts(ref E[] e) { e[] = fronts()[]; }
> 
> The flaw of your design is, the memory to store read bytes/elements is
> allocated by the lower layer.

It's a feature. :)

> E.g. If you want to construct linked list of some some elements, you
> must copy elements from returned slice to new allocated node. I think
> it is still inefficient.
> 
>> depending on which is more efficient. A range can provide
>>
>>  enum bool HasBuffer = 0 || 1;
>>
>> so that the user can pick the more suited alternative.
> 
> I think fewer primitives as possible is better design than adding
> extra/optional primitives.

If you pick just one scheme, then you will end up with an unnecessary
copy sometimes. Or using non-std APIs. Again, I'm saying *both* caller-
owned-buffer *and* range-owned-buffer interfaces should be defined.
Otherwise, code that needs decent performance will not be able to use
the pure range API, and will not interoperate well with "std" code.

> How many primitives in your interface design?

Multi-element versions of front, popFront and puts. I think this
was enough to get things working; this is the tested and proven part.

Then there's 'available' and 'free', so that it's possible to 
avoid blocking. And 'allocate' and 'release', for zero-copy output
streams. But i don't remember if i've actually used these parts, i
don't think i needed them.
This is all from memory, as the last time i worked on this was a while
ago, just before i ran into:

   http://www.digitalmars.com/d/archives/digitalmars/D/dtors_in_shared_structs_fail_to_compile_157978.html

...

>>> And, 'put' primitive in output range concept doesn't support non-blocikng write.
>>> 'put' should consume *all* of given data and write it  to underlying
>>> device, then it would block.
>>
>> True, a write-as-much-as-possible-but not-more primitive is needed.
>>
>>   size_t puts(E[], size_t atleast=size_t.max);
>>
>> or something like that. (Doing it this way allows for explicit
>> non-blocking 'puts', ie '(written=puts(els, 0))==0' means EAGAIN.)
>>
>>> Therefore, whole of range concept doesn't cover non-blocking I/O.
> 
> I can agree for the signatures. but the names 'fronts' and 'puts' are
> a little too similar.

The names are bad, i know... If anybody has better suggestions... (and
almost any other names would be better :) )


artur


More information about the Digitalmars-d mailing list