Re: [rust-dev] Misc questions

2013-02-18 Thread Brian Anderson

On 01/26/2013 03:28 AM, Michael Neumann wrote:

Am 25.01.2013 04:01, schrieb Brian Anderson:

On 01/24/2013 04:37 PM, Patrick Walton wrote:

On 1/24/13 3:55 PM, Michael Neumann wrote:

* I don't like the way libuv is currently integrated into the 
system. It


I've posted a pull request with a proposed redesign of the Rust 
scheduler that integrates the uv event loop directly: 
https://github.com/mozilla/rust/pull/5022

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-02-01 Thread Michael Neumann

Am 30.01.2013 00:43, schrieb Brian Anderson:

On 01/29/2013 04:47 AM, Michael Neumann wrote:

Am 29.01.2013 03:01, schrieb Brian Anderson:

On 01/28/2013 05:29 PM, Graydon Hoare wrote:

On 13-01-28 04:56 PM, Brian Anderson wrote:

I think libuv is doing too much here. For example, if I don't 
want to

remove the socket from the event
queue, just disable the callback, then this is not possible. I'd
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O
availability).

Yet the optimization you suggest has to do with recycling the buffer,
not listening for one kind of event vs. another.

In general I'm not interested in trying to get underneath the
abstraction uv is providing. It's providing an IOCP-oriented 
interface,

I would like to code to that and make the rust IO library not have to
worry when it's on windows vs. unix. That's the point of the 
abstraction
uv provides, and it's valuable. If it means bouncing off epoll a 
few too
many times (or reallocating a buffer a few too many times), I'm not 
too

concerned. Those should both be O(1) operations.

Is it possible to do this optimization later or do we need to plan 
for
this ahead of time? I would prefer to use the uv API as it's 
presented

to start with.

The optimization to use a caller-provided buffer should (a) not be
necessary to get us started and (b) be equally possible on either
platform, unix or windows, _so long as_ we're actually sleeping a task
during its period of interest in IO (either the pre-readiness sleep 
or a

post-issue, pre-completion sleep). In other words, if we're simulating
sync IO, then we can use a task-local buffer. If we're _not_ 
simulating

sync IO (I sure hope we do!) then we should let uv allocate and free
dynamic buffers as it needs them.

But I really hope we wind up structuring it so it simulates sync IO.
We're providing a task abstraction. Users _want_ the sync IO 
abstraction

the same way they want the sequential control flow abstraction.


Presenting the scheduler-originating I/O as synchronous is what I 
intend. I am not sure that we can guarantee that a task is actually 
waiting for I/O when an I/O event occurs that that task is waiting 
for. A task may block on some other unrelated event while the event 
loop is doing I/O. Pseudocode:


let port = IOPort::connect(); // Assume we're doing I/O reads using 
something portlike

while port.recv() {
// Block on a different port, while uv continues doing I/O on 
our behalf

let intermediate_value = some_other_port.recv();
}

This is why I'm imagining that the scheduler will sometimes need to 
buffer.


I don't think so. Let me explain.

This anyway is only a problem (which can be solved) iff we want to be 
able to treat I/O like a
port and want to wait for either one to resume our thread. And I 
assume we want this, so
that we can listen on an I/O socket AND for example for incoming 
messages at the same time.


The kernel provides a way to do (task-local) blocking I/O operations. 
There is no way for the
task to return from a read() call unless data comes in or in case of 
EOF (or any other error
condition).This behaves basically like a blocking POSIX read() call, 
just that it is converted
into asynchronous read by libuv under the hood. To expose I/O as 
port, we have to start

a new task:

  let fd = open(...);
  let (po, ch) = streams::pipe();
  do task::spawn {
loop {
  let buf: ~[u8] = vec::from_fn(1000, || 0);
  let nread = fd.read(buf, 1000);
  if nread  0 {
ch.send(Data(buf))
  }
  else if nread == 0 {
ch.send(EOF)
  }
  else {
ch.send(Error)
  }
}
  }


Yes, a single call to 'read' will not return until some I/O arrives, 
but after 'read' returns I/O continues to arrive and that I/O needs to 
be stored somewhere if the task doesn't immediately block in another 
call to 'read' on that same fd. Taking the above example:


loop {
// This will block until data arrives at which point the task will 
be context-switched in and the data returned.

let nread = fd.read(buf, 1000);

// This will put the task to sleep waiting on a message on cmd_port
let command = cmd_port.recv();
}

Until data arrives on cmd_port the task cannot be scheduled. While the 
task is asleep the I/O loop can't be blocked since other tasks are 
using it too. So in the meantime uv continues to receive data from the 
open fd and it needs to live somewhere until the task calls 'read' 
again on the same fd. Perhaps there's something I don't understand 
about the uv API here, but I think that once we start reading uv is 
going to continually provide us with data whether we are ready for it 
or not.




  // now we can treat `po` as a Port and call select() on it


But I don't think channel I/O will be used that often.

Note that one big advantage is that we can specify the buffer size 
ourself!

When we would let libuv create a  buffer for us, how would 

Re: [rust-dev] Misc questions

2013-01-29 Thread Michael Neumann

Am 29.01.2013 03:01, schrieb Brian Anderson:

On 01/28/2013 05:29 PM, Graydon Hoare wrote:

On 13-01-28 04:56 PM, Brian Anderson wrote:


I think libuv is doing too much here. For example, if I don't want to
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O
availability).

Yet the optimization you suggest has to do with recycling the buffer,
not listening for one kind of event vs. another.

In general I'm not interested in trying to get underneath the
abstraction uv is providing. It's providing an IOCP-oriented interface,
I would like to code to that and make the rust IO library not have to
worry when it's on windows vs. unix. That's the point of the abstraction
uv provides, and it's valuable. If it means bouncing off epoll a few too
many times (or reallocating a buffer a few too many times), I'm not too
concerned. Those should both be O(1) operations.


Is it possible to do this optimization later or do we need to plan for
this ahead of time? I would prefer to use the uv API as it's presented
to start with.

The optimization to use a caller-provided buffer should (a) not be
necessary to get us started and (b) be equally possible on either
platform, unix or windows, _so long as_ we're actually sleeping a task
during its period of interest in IO (either the pre-readiness sleep or a
post-issue, pre-completion sleep). In other words, if we're simulating
sync IO, then we can use a task-local buffer. If we're _not_ simulating
sync IO (I sure hope we do!) then we should let uv allocate and free
dynamic buffers as it needs them.

But I really hope we wind up structuring it so it simulates sync IO.
We're providing a task abstraction. Users _want_ the sync IO abstraction
the same way they want the sequential control flow abstraction.


Presenting the scheduler-originating I/O as synchronous is what I 
intend. I am not sure that we can guarantee that a task is actually 
waiting for I/O when an I/O event occurs that that task is waiting 
for. A task may block on some other unrelated event while the event 
loop is doing I/O. Pseudocode:


let port = IOPort::connect(); // Assume we're doing I/O reads using 
something portlike

while port.recv() {
// Block on a different port, while uv continues doing I/O on our 
behalf

let intermediate_value = some_other_port.recv();
}

This is why I'm imagining that the scheduler will sometimes need to 
buffer.


I don't think so. Let me explain.

This anyway is only a problem (which can be solved) iff we want to be 
able to treat I/O like a
port and want to wait for either one to resume our thread. And I assume 
we want this, so
that we can listen on an I/O socket AND for example for incoming 
messages at the same time.


The kernel provides a way to do (task-local) blocking I/O operations. 
There is no way for the
task to return from a read() call unless data comes in or in case of EOF 
(or any other error
condition). This behaves basically like a blocking POSIX read() call, 
just that it is converted
into asynchronous read by libuv under the hood. To expose I/O as port, 
we have to start

a new task:

  let fd = open(...);
  let (po, ch) = streams::pipe();
  do task::spawn {
loop {
  let buf: ~[u8] = vec::from_fn(1000, || 0);
  let nread = fd.read(buf, 1000);
  if nread  0 {
ch.send(Data(buf))
  }
  else if nread == 0 {
ch.send(EOF)
  }
  else {
ch.send(Error)
  }
}
  }

  // now we can treat `po` as a Port and call select() on it


But I don't think channel I/O will be used that often.

Note that one big advantage is that we can specify the buffer size ourself!
When we would let libuv create a  buffer for us, how would it know the
buffer size? The alloc_cb you provide to libuv upon uv_start_read() will get
a suggested_size parameter passed, but this is 64k by default, and libuv
cannot know what kind of I/O protocol you are handling. When I do
line oriented I/O, I would not need a full 64k buffer allocated for every
read, which in the worst case would only return one byte in it in case
of a very slow sender (send one byte each second). Or is 64k enough
for receiving a very large packet. We clearly want a way to tell the I/O
system how large we expect the packet to be that will arrive over I/O
otherwise this is completely useless IMHO.

We would still have one separate iotask per scheduler. This is a native
thread and runs the I/O loop. There is no way to do that inside the
scheduler as we would block any task while waiting for I/O.
The callbacks like on_read_cb would simply notify the scheduler
that the task that was responsible for doing this read operation
can now resume. As the scheduler lives in another thread
(the thread in which all tasks of that scheduler live in)
and might be active, we need to do some locking here.
When the scheduler gets 

Re: [rust-dev] Misc questions

2013-01-29 Thread Michael Neumann

Am 29.01.2013 02:29, schrieb Graydon Hoare:

On 13-01-28 04:56 PM, Brian Anderson wrote:


I think libuv is doing too much here. For example, if I don't want to
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O
availability).

Yet the optimization you suggest has to do with recycling the buffer,
not listening for one kind of event vs. another.

In general I'm not interested in trying to get underneath the
abstraction uv is providing. It's providing an IOCP-oriented interface,
I would like to code to that and make the rust IO library not have to
worry when it's on windows vs. unix. That's the point of the abstraction
uv provides, and it's valuable. If it means bouncing off epoll a few too
many times (or reallocating a buffer a few too many times), I'm not too
concerned. Those should both be O(1) operations.


I think allocating a buffer performs much better than waking up the 
event loop for
every read, because waking up the event loop involves kernel activity on 
both

scheduler and iotask, while malloc should in most cases be pure user-level.
And allocating buffers allows us to asynchronously continue reading 
while the task
is still doing some computations. If we would allow multiple readers on 
a single port
(do we? I think channels in Go allow that), then we would even have a 
very simple way
to load balance I/O to multiple tasks. This could actually make sense in 
many scenarios.
Kind of work-stealing. And we could build arbitrary pipelines. Of course 
we can
simulate the same by using a dispatcher task, but this would incur some 
overhead.



Is it possible to do this optimization later or do we need to plan for
this ahead of time? I would prefer to use the uv API as it's presented
to start with.

The optimization to use a caller-provided buffer should (a) not be
necessary to get us started and (b) be equally possible on either
platform, unix or windows, _so long as_ we're actually sleeping a task
during its period of interest in IO (either the pre-readiness sleep or a
post-issue, pre-completion sleep). In other words, if we're simulating
sync IO, then we can use a task-local buffer. If we're _not_ simulating
sync IO (I sure hope we do!) then we should let uv allocate and free
dynamic buffers as it needs them.


We are kind of simulating sync IO by using a channel. But IO would be 
async in the

background (if we do not want to wakup the event loop for every read), so we
would need buffers.


But I really hope we wind up structuring it so it simulates sync IO.
We're providing a task abstraction. Users _want_ the sync IO abstraction
the same way they want the sequential control flow abstraction.


Yes. I (now) fully agree.


(Indeed, on an appropriately-behaving system I fully expect task=thread
and sync IO calls=system calls)


If using a SizedChannel(1) each io.recv would correspond to one read() 
syscall, except
that the syscall could have happend long ago. Channels with longer 
queues would mean

that up to n (size of queue) read() calls could have happend.

Regards,

  Michael
___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-29 Thread Michael Neumann

Am 29.01.2013 01:56, schrieb Brian Anderson:

On 01/28/2013 03:37 PM, Michael Neumann wrote:

Am 28.01.2013 22:46, schrieb Brian Anderson:

On 01/26/2013 04:07 AM, Michael Neumann wrote:

Am 26.01.2013 13:01, schrieb Michael Neumann:

Am 26.01.2013 12:28, schrieb Michael Neumann:
Another question: When a task sends a message to another task, 
and this task is waiting exactly for this event, will it directly 
switch to that task, or will it buffer the message?
Sometimes this could be quite handy and efficient. I rember this 
was done in the L4 microkernel (www.l4ka.org), which only allowed 
synchronous IPC. It could make sense to provide a
send_and_receive directive, which sends to the channel and lets 
the scheduler know that it is now waiting for a message to 
receive from another port. So send_and_receive could
directly switch to the other task, and when this does a send back 
to the calling task, it will switch back to it. If you don't have 
send_and_receive as atomic operation, there
is no way to switch back to the other task, as it might still be 
running.


as it might still be running is here of course wrong (as we 
switched to another thread). What I wanted to say is, that it is 
not waiting for any event, so it is not in a blocking state, so that

we cannot directly switch back (matching the recv() and the send()).

Ideally the task that wants to read would do the non-blocking I/O 
itself, and the scheduler would just notify when it can read. 
But I think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() / 
_stop). I think this would be much more efficient and even more 
powerful (one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by 
libuv). So what I would suggest is the following:


  // task
  blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue 
(if not yet done) and block.
// once the scheduler will receive an data is available 
event from the kernel

// it will unblock the task.
// then the task will do an non-blocking read() on it's own.


I'm not that familiar with the uv API. Is there a distinct 'data 
available' event that happens before we start reading? I've been 
assuming that, as you say, we have to control over when the read 
events happen, so we would need to check whether the task initiating 
this read was currently waiting for data, and either buffer it or 
context switch to the task depending on its state.


No there isn't! The reason why, as far as I understand it, lies in 
the way Windows handles reads. In UNIX you get notified, when you can 
read, while in Windows,
you get notified when a read completed, so you are basically doing 
the read asynchronously in the background (saving you another context 
switch to the kernel).
I think this is called Proactor (the UNIX-way is called Reactor). 
libuv wants to do this in a platform-independent way, where the 
programmer who uses libuv does

not have to care about which platform he is working with.

So when we think about this sequence in libuv

  uv_read_start(fd)
  - on_read_cb gets triggered
  uv_read_stop(fd)

what it does internally is the following:

UNIX:

  register event for `fd` in event queue
  epoll()
- allocate buffer
- read(fd, nonblocking)
- call on_read_cb
   unregister event

Windows:

  allocate buffer
  start asynchronous read request
  wait for completion (of any outstanding I/O)
  - call on_read_cb

I think libuv is doing too much here. For example, if I don't want to 
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd 
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O 
availability).


I think a simple hack would be to store the buffer address and size 
of buffer in the uv_handle_t structure:


struct our_handle {
  uv_handle_t handle;
  void *buffer;
  size_t buffer_size;
}

and then have the alloc_cb return that:

static uv_buf_t alloc_cb(uv_handle_t *handle, size_t suggested_size)
{
  struct our_handle *h = (struct our_handle*)handle;
  return uv_buf_init(h-buffer, h-buffer_size);
}

You specify the alloc_cb in uv_read_start(). The only thing that you 
need to consider
is that when on_read_cb gets called, you better call uv_read_stop(), 
otherwise

the buffer could be overwritten the next time.

Well, yes, this should work for both UNIX and Windows. If you need 
specific help, let me know.
I've been hacking a lot with libuv lately and I can't wait using 
async I/O in rust (which actually

performs well).



Is it possible to do this optimization later or do we need to plan for 
this ahead of time? I would prefer to use the uv API as it's presented 
to start with.


I welcome any help here. One important and big step we need to get 
through before trying to integrate uv into the scheduler is to create 
safe Rust bindings 

Re: [rust-dev] Misc questions

2013-01-28 Thread Graydon Hoare
On 13-01-26 04:01 AM, Michael Neumann wrote:

 So what I would suggest is the following:
 
   // task
   blocking_read(socket, buffer, ...)
 // this will register socket with the schedulers event queue (if not
 yet done) and block.
 // once the scheduler will receive an data is available event from
 the kernel
 // it will unblock the task.
 // then the task will do an non-blocking read() on it's own.
 
 Basically it's the same what libuv does internally on it's own, just
 that the responsibility for doing the read's for example is
 moved into the task itself, so there is no longer a need for an I/O task
 and we gain full control of the asynchronous reads.
 
 The advantage is:
 
   * we no longer need messages for I/O.
   * more flexibility
   * much better memory usage (no need to copy anymore)
   * the design is much easier and better to understand,
  libraries become so much easier
 
 Maybe that's just what you want to implement with the scheduler rewrite?

That is what I would like to see in the rewrite, yes. It will require
some care in designing the event-loop interface that's called from the
rust-library side, but I think it ought to work.

Really we should have done this from the beginning, we just (wrongly)
deferred attention to AIO library work too long and wound up already
having grown a (duplicate) task scheduler that didn't integrate an IO
event loop. So now we have to de-duplicate them.

-Graydon

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-28 Thread Brian Anderson

On 01/26/2013 03:28 AM, Michael Neumann wrote:

Am 25.01.2013 04:01, schrieb Brian Anderson:

On 01/24/2013 04:37 PM, Patrick Walton wrote:

On 1/24/13 3:55 PM, Michael Neumann wrote:

* I don't like the way libuv is currently integrated into the 
system. It


I sympathize.


:)




works, but performance is
quite low and IMHO the blocking interface is not very usable. For
example I want to write a process
that accepts messages from other processes, and then writes 
something to

the socket or reads from
the socket. This will currently not work, as reading from the socket
will block the process, and
then no more requests can be sent to the process.
So instead of using the read() / write() API of an io::Reader, I'd
prefer to expose the read/write
events of libuv via messages (this is already done between the iotask
and the read()/write() methods,
but it is not accessible to the end-user).

So instead of:

io.read(...)

one would simply write:

readport.recv()


Both of these are blocking. The significant advantage of using a port 
here though is that core::pipes has several ways to receive on 
multiple ports at once, so you could wait for both the read event and 
some other signal instead of being stuck until the read either 
succeeds or times out.


Exactly!

There is a lot of overlap functionally between Port/Chan and 
Reader/Writer (std::flatpipes event implements the GenericPort and 
GenericChan traits on top of Reader and Writer). They both have their 
strengths though. Streaming data over channels is just going to leave 
you with a bunch of byte buffers, whereas Readers give you lots of 
control over how to interpret those bytes. I could see the api here 
being channel based, letting the user opt into Reader and Writer 
implementations for Port~[u8] etc. as needed.




The same for writes. EOF results in closing the readport. The question
is how these messages
should look like to be usable for the programmer (how to handle 
errors?).


What do you think?

Actually there would be connecting ports, which receive events 
whenever

a new connection is established.
A successfully established connection would then be represented by a
readport and writechannel.


brson is working on a rewrite of the scheduler. This new scheduler 
should run directly on the libuv event loop. This should have much 
higher performance.


The current implementation will go away completely. It was useful as 
a prototype but it has problems. The new intent is to design the Rust 
scheduler so that it can be driven by an arbitrary event loop, then 
use the uv event loop for that purpose by default (this could also be 
useful for e.g. integrating with the Win32 event loop, etc.). The 
main advantage of integrating uv into the scheduler over the current 
design is that there will be much less synchronization and context 
switching to dispatch events. This will unfortunately necessitate a 
complex integration of the scheduler and the I/O system and I don't 
know how that is going to work yet.


Do you mean that the libuv callbacks - messages conversion will then 
go away? Currenctly every callback is sent from the iotask to the 
interested task via a message.
Will the new design be different in this regard? I'd like to hear more 
details about what you are trying to accomplish. Is there already some 
code?




The callback to message conversion will go away I think since the 
callbacks will be happening on the same thread where the data is sent or 
received. What I am trying to accomplish is minimize the overhead 
imposed by the Rust runtime, particularly regarding context switches and 
locking. There is no code yet as I am still working through some blockers.


I am not even sure how it's going to work, I just have a vague idea that 
our scheduler threads are event loops, and uv is an event loop, so let's 
plug them together. I think the transition from uv's asynchronous 
callbacks to Rust imperative code will still involve a buffered message 
object that gets returned to Rust code through a context switch, but it 
won't involve nearly as much work to do the handoff since the I/O work 
and the Rust code is both running in the same thread.


I think another thing that currently makes I/O slow is that for every 
read() call, at first a messages is sent to the iotask to let the 
libuv know that we want to start reading from
the socket (uv_read_start()). Then we actually request the read 
(another message) and finally we stop reading again (uv_read_stop()). 
So in total, every read will involve 3 complete
message cycles between iotask and the requesting task. I hope this 
will be going to be reduced to just one (but this is probably just a 
library issue and not related to the scheduler...).


This overhead should go away I think.



Another question: When a task sends a message to another task, and 
this task is waiting exactly for this event, will it directly switch 
to that task, or will it buffer the message?


It will not directly 

Re: [rust-dev] Misc questions

2013-01-28 Thread Brian Anderson

On 01/26/2013 04:07 AM, Michael Neumann wrote:

Am 26.01.2013 13:01, schrieb Michael Neumann:

Am 26.01.2013 12:28, schrieb Michael Neumann:
Another question: When a task sends a message to another task, and 
this task is waiting exactly for this event, will it directly switch 
to that task, or will it buffer the message?
Sometimes this could be quite handy and efficient. I rember this was 
done in the L4 microkernel (www.l4ka.org), which only allowed 
synchronous IPC. It could make sense to provide a
send_and_receive directive, which sends to the channel and lets the 
scheduler know that it is now waiting for a message to receive from 
another port. So send_and_receive could
directly switch to the other task, and when this does a send back to 
the calling task, it will switch back to it. If you don't have 
send_and_receive as atomic operation, there
is no way to switch back to the other task, as it might still be 
running.


as it might still be running is here of course wrong (as we 
switched to another thread). What I wanted to say is, that it is not 
waiting for any event, so it is not in a blocking state, so that

we cannot directly switch back (matching the recv() and the send()).

Ideally the task that wants to read would do the non-blocking I/O 
itself, and the scheduler would just notify when it can read. But I 
think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() / 
_stop). I think this would be much more efficient and even more 
powerful (one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by 
libuv). So what I would suggest is the following:


  // task
  blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue (if 
not yet done) and block.
// once the scheduler will receive an data is available event 
from the kernel

// it will unblock the task.
// then the task will do an non-blocking read() on it's own.


I'm not that familiar with the uv API. Is there a distinct 'data 
available' event that happens before we start reading? I've been 
assuming that, as you say, we have to control over when the read events 
happen, so we would need to check whether the task initiating this read 
was currently waiting for data, and either buffer it or context switch 
to the task depending on its state.




Basically it's the same what libuv does internally on it's own, just 
that the responsibility for doing the read's for example is
moved into the task itself, so there is no longer a need for an I/O 
task and we gain full control of the asynchronous reads.


The advantage is:

  * we no longer need messages for I/O.
  * more flexibility
  * much better memory usage (no need to copy anymore)


Agree on these points.


  * the design is much easier and better to understand,
 libraries become so much easier


I do not think the design that integrates the scheduler with the I/O 
loop will be easier to understand. I expect the interactions will be 
complicated.




* and message passing could be done synchronous, i.e. very fast :)


In many cases this should be true, if the task initiating the I/O is 
still waiting on it. It could go off and execute other code and block on 
other things though, in which case the data needs to be buffered until 
the task can be scheduled.


___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-28 Thread Brian Anderson

On 01/28/2013 03:37 PM, Michael Neumann wrote:

Am 28.01.2013 22:46, schrieb Brian Anderson:

On 01/26/2013 04:07 AM, Michael Neumann wrote:

Am 26.01.2013 13:01, schrieb Michael Neumann:

Am 26.01.2013 12:28, schrieb Michael Neumann:
Another question: When a task sends a message to another task, and 
this task is waiting exactly for this event, will it directly 
switch to that task, or will it buffer the message?
Sometimes this could be quite handy and efficient. I rember this 
was done in the L4 microkernel (www.l4ka.org), which only allowed 
synchronous IPC. It could make sense to provide a
send_and_receive directive, which sends to the channel and lets 
the scheduler know that it is now waiting for a message to receive 
from another port. So send_and_receive could
directly switch to the other task, and when this does a send back 
to the calling task, it will switch back to it. If you don't have 
send_and_receive as atomic operation, there
is no way to switch back to the other task, as it might still be 
running.


as it might still be running is here of course wrong (as we 
switched to another thread). What I wanted to say is, that it is 
not waiting for any event, so it is not in a blocking state, so that

we cannot directly switch back (matching the recv() and the send()).

Ideally the task that wants to read would do the non-blocking I/O 
itself, and the scheduler would just notify when it can read. But 
I think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() / 
_stop). I think this would be much more efficient and even more 
powerful (one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by 
libuv). So what I would suggest is the following:


  // task
  blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue 
(if not yet done) and block.
// once the scheduler will receive an data is available event 
from the kernel

// it will unblock the task.
// then the task will do an non-blocking read() on it's own.


I'm not that familiar with the uv API. Is there a distinct 'data 
available' event that happens before we start reading? I've been 
assuming that, as you say, we have to control over when the read 
events happen, so we would need to check whether the task initiating 
this read was currently waiting for data, and either buffer it or 
context switch to the task depending on its state.


No there isn't! The reason why, as far as I understand it, lies in the 
way Windows handles reads. In UNIX you get notified, when you can 
read, while in Windows,
you get notified when a read completed, so you are basically doing the 
read asynchronously in the background (saving you another context 
switch to the kernel).
I think this is called Proactor (the UNIX-way is called Reactor). 
libuv wants to do this in a platform-independent way, where the 
programmer who uses libuv does

not have to care about which platform he is working with.

So when we think about this sequence in libuv

  uv_read_start(fd)
  - on_read_cb gets triggered
  uv_read_stop(fd)

what it does internally is the following:

UNIX:

  register event for `fd` in event queue
  epoll()
- allocate buffer
- read(fd, nonblocking)
- call on_read_cb
   unregister event

Windows:

  allocate buffer
  start asynchronous read request
  wait for completion (of any outstanding I/O)
  - call on_read_cb

I think libuv is doing too much here. For example, if I don't want to 
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd 
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O 
availability).


I think a simple hack would be to store the buffer address and size of 
buffer in the uv_handle_t structure:


struct our_handle {
  uv_handle_t handle;
  void *buffer;
  size_t buffer_size;
}

and then have the alloc_cb return that:

static uv_buf_t alloc_cb(uv_handle_t *handle, size_t suggested_size)
{
  struct our_handle *h = (struct our_handle*)handle;
  return uv_buf_init(h-buffer, h-buffer_size);
}

You specify the alloc_cb in uv_read_start(). The only thing that you 
need to consider
is that when on_read_cb gets called, you better call uv_read_stop(), 
otherwise

the buffer could be overwritten the next time.

Well, yes, this should work for both UNIX and Windows. If you need 
specific help, let me know.
I've been hacking a lot with libuv lately and I can't wait using async 
I/O in rust (which actually

performs well).



Is it possible to do this optimization later or do we need to plan for 
this ahead of time? I would prefer to use the uv API as it's presented 
to start with.


I welcome any help here. One important and big step we need to get 
through before trying to integrate uv into the scheduler is to create 
safe Rust bindings to libuv. Last time around we coded directly 

Re: [rust-dev] Misc questions

2013-01-28 Thread Graydon Hoare
On 13-01-28 04:56 PM, Brian Anderson wrote:

 I think libuv is doing too much here. For example, if I don't want to
 remove the socket from the event
 queue, just disable the callback, then this is not possible. I'd
 prefer when I could just tell libuv that
 I am interested in event X (on Windows: I/O completion, on UNIX: I/O
 availability).

Yet the optimization you suggest has to do with recycling the buffer,
not listening for one kind of event vs. another.

In general I'm not interested in trying to get underneath the
abstraction uv is providing. It's providing an IOCP-oriented interface,
I would like to code to that and make the rust IO library not have to
worry when it's on windows vs. unix. That's the point of the abstraction
uv provides, and it's valuable. If it means bouncing off epoll a few too
many times (or reallocating a buffer a few too many times), I'm not too
concerned. Those should both be O(1) operations.

 Is it possible to do this optimization later or do we need to plan for
 this ahead of time? I would prefer to use the uv API as it's presented
 to start with.

The optimization to use a caller-provided buffer should (a) not be
necessary to get us started and (b) be equally possible on either
platform, unix or windows, _so long as_ we're actually sleeping a task
during its period of interest in IO (either the pre-readiness sleep or a
post-issue, pre-completion sleep). In other words, if we're simulating
sync IO, then we can use a task-local buffer. If we're _not_ simulating
sync IO (I sure hope we do!) then we should let uv allocate and free
dynamic buffers as it needs them.

But I really hope we wind up structuring it so it simulates sync IO.
We're providing a task abstraction. Users _want_ the sync IO abstraction
the same way they want the sequential control flow abstraction.

(Indeed, on an appropriately-behaving system I fully expect task=thread
and sync IO calls=system calls)

 I welcome any help here. One important and big step we need to get
 through before trying to integrate uv into the scheduler is to create
 safe Rust bindings to libuv. Last time around we coded directly to the
 uv API and it resulted in some big maintenance problems (unsafe code
 everywhere). pfox is working on updating libuv to upstream trunk now,
 after which I expect somebody will start on the bindings. If you want to
 discuss the integration of uv into the scheduler there is an issue open:
 https://github.com/mozilla/rust/issues/4419.

I'll try to remain more-involved this time. We have to get this right.

-Graydon

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-28 Thread Brian Anderson

On 01/28/2013 05:29 PM, Graydon Hoare wrote:

On 13-01-28 04:56 PM, Brian Anderson wrote:


I think libuv is doing too much here. For example, if I don't want to
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O
availability).

Yet the optimization you suggest has to do with recycling the buffer,
not listening for one kind of event vs. another.

In general I'm not interested in trying to get underneath the
abstraction uv is providing. It's providing an IOCP-oriented interface,
I would like to code to that and make the rust IO library not have to
worry when it's on windows vs. unix. That's the point of the abstraction
uv provides, and it's valuable. If it means bouncing off epoll a few too
many times (or reallocating a buffer a few too many times), I'm not too
concerned. Those should both be O(1) operations.


Is it possible to do this optimization later or do we need to plan for
this ahead of time? I would prefer to use the uv API as it's presented
to start with.

The optimization to use a caller-provided buffer should (a) not be
necessary to get us started and (b) be equally possible on either
platform, unix or windows, _so long as_ we're actually sleeping a task
during its period of interest in IO (either the pre-readiness sleep or a
post-issue, pre-completion sleep). In other words, if we're simulating
sync IO, then we can use a task-local buffer. If we're _not_ simulating
sync IO (I sure hope we do!) then we should let uv allocate and free
dynamic buffers as it needs them.

But I really hope we wind up structuring it so it simulates sync IO.
We're providing a task abstraction. Users _want_ the sync IO abstraction
the same way they want the sequential control flow abstraction.


Presenting the scheduler-originating I/O as synchronous is what I 
intend. I am not sure that we can guarantee that a task is actually 
waiting for I/O when an I/O event occurs that that task is waiting for. 
A task may block on some other unrelated event while the event loop is 
doing I/O. Pseudocode:


let port = IOPort::connect(); // Assume we're doing I/O reads using 
something portlike

while port.recv() {
// Block on a different port, while uv continues doing I/O on our 
behalf

let intermediate_value = some_other_port.recv();
}

This is why I'm imagining that the scheduler will sometimes need to buffer.



(Indeed, on an appropriately-behaving system I fully expect task=thread
and sync IO calls=system calls)


I welcome any help here. One important and big step we need to get
through before trying to integrate uv into the scheduler is to create
safe Rust bindings to libuv. Last time around we coded directly to the
uv API and it resulted in some big maintenance problems (unsafe code
everywhere). pfox is working on updating libuv to upstream trunk now,
after which I expect somebody will start on the bindings. If you want to
discuss the integration of uv into the scheduler there is an issue open:
https://github.com/mozilla/rust/issues/4419.

I'll try to remain more-involved this time. We have to get this right.

-Graydon

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-26 Thread Michael Neumann

Am 25.01.2013 01:37, schrieb Patrick Walton:

On 1/24/13 3:55 PM, Michael Neumann wrote:

Hi,

Again a couple of random question...

* Would it be possible to optimize this kind of enum (two cases, where
one case contains a borrowed pointer)
into a simple pointer, where None would be represented as the null 
pointer?


enum OptionA {
None,
Some(~A)
}

As the pointer to A can never be null it should be possible. This
probably wouldn't affect performance much,
but when storing it into an Array that would save a lot of space
(basically cut down space usage half).


Yes. This has been on the agenda for years. The reason why we don't 
make any guarantees as to the memory layout for enums is precisely so 
that we can implement optimizations like this.


Great to know that this will eventually be implemented.


* match() statements. I think the order in which the matches are
performed are important. But when I have
a very simple statement like this:

match io.read_char() as u8 {
0x0c = ...,
0x0d = ...,
0x0f .. 0x1a =
...
}

will the compiler construct an efficient goto jump table or will it
construct sequential if statements instead?
´ My question is if it makes sense to reorder more frequent cases to the
top or not.


LLVM will construct a jump table. I've verified this in my NES emulator.


Great. Hopefully it merges common statements as well. That is for
  0x00 .. 0x0f = io::println(...)
it don't generate 15 separate io::println instructions. I assume it
generates a table which contains IP offsets.



Also I wonder why I get a non-exhaustive patterns error message for
this one:

match c as u8 {
0 .. 255 = 1
}


The exhaustiveness checker currently doesn't know about integer 
ranges. This is probably a bug.


It's unituitive, so I think it's a bug :)


* Using str::as_bytes()

I cannot get str::as_bytes working. The example in the documention is
not working for several reasons (wrong syntax...)

I tried this:

fn write(buf: ~[u8]) {
io::println(fmt!(%?, buf));
}

fn main() {
let mystr = ~Hello World;
do str::as_bytes(mystr) |bytes| {
write(*bytes);
}
}

But get the compiler error:

t.rs:8:10: 8:16 error: moving out of dereference of immutable  pointer
t.rs:8 write(*bytes);
^~


I think you want `[u8]`.


Of course! I feel little stupid now *g*.


* What exaclty is the semantic of as? Is it like a C-cast?

Imagine if I have

let b: u8 = 255;
let s: i8 = b as i8;

This gives -1 for s. But when I do b as i32, it gives 255.
If I want to keep the sign I have to do (b as i8) as i32.


It's supposed to be like a C cast. This seems like a bug to me.


Hm, for me it makes sense somehow.

I think that every cast from unsigned to signed (or vice versa)
will first extend the size to the requested size so that:

  u8 as i32 equivalent to (u8 as u32) as i32

and this is clearly different to (u8 as i8) as i32, because
i8 as i32 will sign-extend, i.e. keep the upper bit.

Thanks for your answers,

  Michael
___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-26 Thread Michael Neumann

Am 26.01.2013 12:28, schrieb Michael Neumann:
Another question: When a task sends a message to another task, and 
this task is waiting exactly for this event, will it directly switch 
to that task, or will it buffer the message?
Sometimes this could be quite handy and efficient. I rember this was 
done in the L4 microkernel (www.l4ka.org), which only allowed 
synchronous IPC. It could make sense to provide a
send_and_receive directive, which sends to the channel and lets the 
scheduler know that it is now waiting for a message to receive from 
another port. So send_and_receive could
directly switch to the other task, and when this does a send back to 
the calling task, it will switch back to it. If you don't have 
send_and_receive as atomic operation, there

is no way to switch back to the other task, as it might still be running.


as it might still be running is here of course wrong (as we switched 
to another thread). What I wanted to say is, that it is not waiting for 
any event, so it is not in a blocking state, so that

we cannot directly switch back (matching the recv() and the send()).

Ideally the task that wants to read would do the non-blocking I/O 
itself, and the scheduler would just notify when it can read. But I 
think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() / 
_stop). I think this would be much more efficient and even more powerful 
(one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by 
libuv). So what I would suggest is the following:


  // task
  blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue (if 
not yet done) and block.
// once the scheduler will receive an data is available event 
from the kernel

// it will unblock the task.
// then the task will do an non-blocking read() on it's own.

Basically it's the same what libuv does internally on it's own, just 
that the responsibility for doing the read's for example is
moved into the task itself, so there is no longer a need for an I/O task 
and we gain full control of the asynchronous reads.


The advantage is:

  * we no longer need messages for I/O.
  * more flexibility
  * much better memory usage (no need to copy anymore)
  * the design is much easier and better to understand,
 libraries become so much easier

Maybe that's just what you want to implement with the scheduler rewrite?

Best,

  Michael
___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-26 Thread Michael Neumann

Am 26.01.2013 13:01, schrieb Michael Neumann:

Am 26.01.2013 12:28, schrieb Michael Neumann:
Another question: When a task sends a message to another task, and 
this task is waiting exactly for this event, will it directly switch 
to that task, or will it buffer the message?
Sometimes this could be quite handy and efficient. I rember this was 
done in the L4 microkernel (www.l4ka.org), which only allowed 
synchronous IPC. It could make sense to provide a
send_and_receive directive, which sends to the channel and lets the 
scheduler know that it is now waiting for a message to receive from 
another port. So send_and_receive could
directly switch to the other task, and when this does a send back to 
the calling task, it will switch back to it. If you don't have 
send_and_receive as atomic operation, there
is no way to switch back to the other task, as it might still be 
running.


as it might still be running is here of course wrong (as we switched 
to another thread). What I wanted to say is, that it is not waiting 
for any event, so it is not in a blocking state, so that

we cannot directly switch back (matching the recv() and the send()).

Ideally the task that wants to read would do the non-blocking I/O 
itself, and the scheduler would just notify when it can read. But I 
think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() / 
_stop). I think this would be much more efficient and even more 
powerful (one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by 
libuv). So what I would suggest is the following:


  // task
  blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue (if 
not yet done) and block.
// once the scheduler will receive an data is available event 
from the kernel

// it will unblock the task.
// then the task will do an non-blocking read() on it's own.

Basically it's the same what libuv does internally on it's own, just 
that the responsibility for doing the read's for example is
moved into the task itself, so there is no longer a need for an I/O 
task and we gain full control of the asynchronous reads.


The advantage is:

  * we no longer need messages for I/O.
  * more flexibility
  * much better memory usage (no need to copy anymore)
  * the design is much easier and better to understand,
 libraries become so much easier


* and message passing could be done synchronous, i.e. very fast :)


___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


[rust-dev] Misc questions

2013-01-24 Thread Michael Neumann

Hi,

Again a couple of random question...

* Would it be possible to optimize this kind of enum (two cases, where 
one case contains a borrowed pointer)

into a simple pointer, where None would be represented as the null pointer?

enum OptionA {
None,
Some(~A)
}

As the pointer to A can never be null it should be possible. This 
probably wouldn't affect performance much,
but when storing it into an Array that would save a lot of space 
(basically cut down space usage half).


* match() statements. I think the order in which the matches are 
performed are important. But when I have

a very simple statement like this:

match io.read_char() as u8 {
0x0c = ...,
0x0d = ...,
0x0f .. 0x1a =
...
}

will the compiler construct an efficient goto jump table or will it 
construct sequential if statements instead?
´ My question is if it makes sense to reorder more frequent cases to the 
top or not.


Also I wonder why I get a non-exhaustive patterns error message for 
this one:


match c as u8 {
0 .. 255 = 1
}

* Using str::as_bytes()

I cannot get str::as_bytes working. The example in the documention is 
not working for several reasons (wrong syntax...)


I tried this:

fn write(buf: ~[u8]) {
io::println(fmt!(%?, buf));
}

fn main() {
let mystr = ~Hello World;
do str::as_bytes(mystr) |bytes| {
write(*bytes);
}
}

But get the compiler error:

t.rs:8:10: 8:16 error: moving out of dereference of immutable  pointer
t.rs:8 write(*bytes);
^~

* A ~str is internally represented by an ~[u8] vector.
It is basically a heap-allocated

struct rust_vec {
size_t fill;
size_t alloc;
uint8_t data[];
}

When I correctly read the code the string is allocated in-place, i.e. 
for a string of size 5,
you will allocate sizeof(struct rust_vec) + 5 + 1 bytes. So the data 
pointer points past the
data. As a reallocation can change the pointer to the rust_vec, I know 
understand why
I have to pass sometimes a mut ~[T]into a function. In case the 
reallocation returns a new

pointer, the passed pointer has to be updated.

I guess slices use the same rust_vec struct, but allocated on the stack, 
where data points to

another ~vectors data.

What I don't understand is the following comment for struct rust_vec:

size_t fill; // in bytes; if zero, heapified

Please correct me if I am wrong.

* There is a severe performance bug in TcpSocketBuf.read(). I am trying 
to fix it right now myself,
once I am done, I will do another post. This explains why I get very bad 
network I/O performance.
Basically the function copies the internal buffer over and over again, 
once for each call.
This is especially bad when using read_line(), as it calls read() for 
every byte.


* What exaclty is the semantic of as? Is it like a C-cast?

Imagine if I have

let b: u8 = 255;
let s: i8 = b as i8;

This gives -1 for s. But when I do b as i32, it gives 255.
If I want to keep the sign I have to do (b as i8) as i32.

* I don't like the way libuv is currently integrated into the system. It 
works, but performance is
quite low and IMHO the blocking interface is not very usable. For 
example I want to write a process
that accepts messages from other processes, and then writes something to 
the socket or reads from
the socket. This will currently not work, as reading from the socket 
will block the process, and

then no more requests can be sent to the process.
So instead of using the read() / write() API of an io::Reader, I'd 
prefer to expose the read/write
events of libuv via messages (this is already done between the iotask 
and the read()/write() methods,

but it is not accessible to the end-user).

So instead of:

io.read(...)

one would simply write:

readport.recv()

The same for writes. EOF results in closing the readport. The question 
is how these messages

should look like to be usable for the programmer (how to handle errors?).

What do you think?

Actually there would be connecting ports, which receive events whenever 
a new connection is established.
A successfully established connection would then be represented by a 
readport and writechannel.


* I'd like to know more how the task scheduler and the pipes work 
together. Is there any info available somewhere?
Also, if I would create a native pthread in C, could I simply call an 
external rust function?


Best,

Michael
___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions

2013-01-24 Thread Patrick Walton

On 1/24/13 3:55 PM, Michael Neumann wrote:

Hi,

Again a couple of random question...

* Would it be possible to optimize this kind of enum (two cases, where
one case contains a borrowed pointer)
into a simple pointer, where None would be represented as the null pointer?

enum OptionA {
None,
Some(~A)
}

As the pointer to A can never be null it should be possible. This
probably wouldn't affect performance much,
but when storing it into an Array that would save a lot of space
(basically cut down space usage half).


Yes. This has been on the agenda for years. The reason why we don't make 
any guarantees as to the memory layout for enums is precisely so that we 
can implement optimizations like this.



* match() statements. I think the order in which the matches are
performed are important. But when I have
a very simple statement like this:

match io.read_char() as u8 {
0x0c = ...,
0x0d = ...,
0x0f .. 0x1a =
...
}

will the compiler construct an efficient goto jump table or will it
construct sequential if statements instead?
´ My question is if it makes sense to reorder more frequent cases to the
top or not.


LLVM will construct a jump table. I've verified this in my NES emulator.



Also I wonder why I get a non-exhaustive patterns error message for
this one:

match c as u8 {
0 .. 255 = 1
}


The exhaustiveness checker currently doesn't know about integer ranges. 
This is probably a bug.



* Using str::as_bytes()

I cannot get str::as_bytes working. The example in the documention is
not working for several reasons (wrong syntax...)

I tried this:

fn write(buf: ~[u8]) {
io::println(fmt!(%?, buf));
}

fn main() {
let mystr = ~Hello World;
do str::as_bytes(mystr) |bytes| {
write(*bytes);
}
}

But get the compiler error:

t.rs:8:10: 8:16 error: moving out of dereference of immutable  pointer
t.rs:8 write(*bytes);
^~


I think you want `[u8]`.



* A ~str is internally represented by an ~[u8] vector.
It is basically a heap-allocated

struct rust_vec {
size_t fill;
size_t alloc;
uint8_t data[];
}

When I correctly read the code the string is allocated in-place, i.e.
for a string of size 5,
you will allocate sizeof(struct rust_vec) + 5 + 1 bytes. So the data
pointer points past the
data. As a reallocation can change the pointer to the rust_vec, I know
understand why
I have to pass sometimes a mut ~[T]into a function. In case the
reallocation returns a new
pointer, the passed pointer has to be updated.

I guess slices use the same rust_vec struct, but allocated on the stack,
where data points to
another ~vectors data.

What I don't understand is the following comment for struct rust_vec:

size_t fill; // in bytes; if zero, heapified


This comment is incorrect. It refers to the representation in Rust as of 
June 2012, instead of today.



Please correct me if I am wrong.

* There is a severe performance bug in TcpSocketBuf.read(). I am trying
to fix it right now myself,
once I am done, I will do another post. This explains why I get very bad
network I/O performance.
Basically the function copies the internal buffer over and over again,
once for each call.
This is especially bad when using read_line(), as it calls read() for
every byte.


Ah, that's indeed very bad. Patches welcome :)


* What exaclty is the semantic of as? Is it like a C-cast?

Imagine if I have

let b: u8 = 255;
let s: i8 = b as i8;

This gives -1 for s. But when I do b as i32, it gives 255.
If I want to keep the sign I have to do (b as i8) as i32.


It's supposed to be like a C cast. This seems like a bug to me.


* I don't like the way libuv is currently integrated into the system. It
works, but performance is
quite low and IMHO the blocking interface is not very usable. For
example I want to write a process
that accepts messages from other processes, and then writes something to
the socket or reads from
the socket. This will currently not work, as reading from the socket
will block the process, and
then no more requests can be sent to the process.
So instead of using the read() / write() API of an io::Reader, I'd
prefer to expose the read/write
events of libuv via messages (this is already done between the iotask
and the read()/write() methods,
but it is not accessible to the end-user).

So instead of:

io.read(...)

one would simply write:

readport.recv()

The same for writes. EOF results in closing the readport. The question
is how these messages
should look like to be usable for the programmer (how to handle errors?).

What do you think?

Actually there would be connecting ports, which receive events whenever
a new connection is established.
A successfully established connection would then be represented by a
readport and writechannel.


brson is working on a rewrite of the scheduler. This new scheduler 
should run directly on the libuv event loop. This should have much 
higher performance.




* I'd like to know more how the task scheduler and the pipes work
together. Is there any info available somewhere?


I think brson knows 

Re: [rust-dev] Misc questions

2013-01-24 Thread Brian Anderson

On 01/24/2013 04:37 PM, Patrick Walton wrote:

On 1/24/13 3:55 PM, Michael Neumann wrote:


* I don't like the way libuv is currently integrated into the system. It


I sympathize.


works, but performance is
quite low and IMHO the blocking interface is not very usable. For
example I want to write a process
that accepts messages from other processes, and then writes something to
the socket or reads from
the socket. This will currently not work, as reading from the socket
will block the process, and
then no more requests can be sent to the process.
So instead of using the read() / write() API of an io::Reader, I'd
prefer to expose the read/write
events of libuv via messages (this is already done between the iotask
and the read()/write() methods,
but it is not accessible to the end-user).

So instead of:

io.read(...)

one would simply write:

readport.recv()


Both of these are blocking. The significant advantage of using a port 
here though is that core::pipes has several ways to receive on multiple 
ports at once, so you could wait for both the read event and some other 
signal instead of being stuck until the read either succeeds or times out.


There is a lot of overlap functionally between Port/Chan and 
Reader/Writer (std::flatpipes event implements the GenericPort and 
GenericChan traits on top of Reader and Writer). They both have their 
strengths though. Streaming data over channels is just going to leave 
you with a bunch of byte buffers, whereas Readers give you lots of 
control over how to interpret those bytes. I could see the api here 
being channel based, letting the user opt into Reader and Writer 
implementations for Port~[u8] etc. as needed.




The same for writes. EOF results in closing the readport. The question
is how these messages
should look like to be usable for the programmer (how to handle 
errors?).


What do you think?

Actually there would be connecting ports, which receive events whenever
a new connection is established.
A successfully established connection would then be represented by a
readport and writechannel.


brson is working on a rewrite of the scheduler. This new scheduler 
should run directly on the libuv event loop. This should have much 
higher performance.


The current implementation will go away completely. It was useful as a 
prototype but it has problems. The new intent is to design the Rust 
scheduler so that it can be driven by an arbitrary event loop, then use 
the uv event loop for that purpose by default (this could also be useful 
for e.g. integrating with the Win32 event loop, etc.). The main 
advantage of integrating uv into the scheduler over the current design 
is that there will be much less synchronization and context switching to 
dispatch events. This will unfortunately necessitate a complex 
integration of the scheduler and the I/O system and I don't know how 
that is going to work yet.







* I'd like to know more how the task scheduler and the pipes work
together. Is there any info available somewhere?


I think brson knows best.


There's no info besides the source code. The relevant files are 
`src/libcore/pipes.rs` and `src/rt/rust_task.cpp`. Pipes uses three 
foreign calls to indirectly control the scheduler: `task_wait_event`, 
`task_signal_event`, `task_clear_event_reject`, the details of which I 
don't know off hand but which look fairly straightforward. The 
implementation is made more complex by the continued existence of 
`core::oldcomm`, which uses a slightly different method of signalling 
events and relies on much more foreign code.





Also, if I would create a native pthread in C, could I simply call an
external rust function?




Not yet. Today all Rust code depends on the Rust runtime (I've been 
saying that code must be run 'in task context'). Creating a pthread puts 
you outside of a task context. Being in task context essentially means 
that various runtime calls are able to locate a pointer to `rust_task` 
in TLS, and of course that pointer must be set up and managed correctly. 
It's not something you can do manually.


We are slowly working on 'freestanding Rust', which will let you run 
Rust code without the runtime. This is particularly needed at the moment 
to port the Rust runtime to Rust. The first steps are in this pull request:


https://github.com/mozilla/rust/pull/4619

After that pull request you can make foreign calls and use the exchange 
heap outside of task context, but if you do call a function that 
requires the runtime the process will abort.


___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


[rust-dev] Misc questions and ideas

2012-12-23 Thread Michael Neumann
Hi,

I've spent the last days hacking in Rust and a few questions and ideas
have accumulated over that time.

* If I use unique ~pointers, there is absolutely no runtime overhead,
  so neither ref-counting nor GC is involved, right?

* Heap-allocated pointers incur ref-counting. So when I pass a
  @pointer, I will basically pass a

struct heap_ptr {ptr: *byte, cnt: uint}

  around. Right?

* vec::build_sized() somehow seems to be pretty slow. When I use it,
  instead of a for() loop, my rust-msgpack library slows down by 
  factor 2 for loading msgpack data.

  Also, I would have expected that vec::build_sized() will call my
  supplied function n times. IMHO the name is little bit
  misleading here.

* I do not fully understand the warning of the following script:

  fn main() {
let bytes =
  io::read_whole_file(path::Path(/tmp/matching.msgpack)).get();
  }

  t2.rs:2:14: 2:78 warning: instantiating copy type parameter with a not
  implicitly copyable type t2.rs:2   let bytes =
  io::read_whole_file(path::Path(/tmp/matching.msgpack)).get();
  ^~~~

  Does it mean that it will copy the ~str again? When I use pattern
  matching instead of get(), I don't get this warning, but it seems to
  be slower. Will it just silence the warning???

* This is also strange to me:

  fn nowarn(bytes: [u8]) {}

  fn main() {
let bytes = ~[1,2,3];
nowarn(bytes);
let br = io::BytesReader { bytes: bytes, pos: 0 }; // FAILS
  }

  t.rs:6:36: 6:41 error: mismatched types: expected `/[u8]` but found
  `~[u8]` ([] storage differs: expected  but found ~) t.rs:6   let br =
  io::BytesReader { bytes: bytes, pos: 0 }; ^

  It implicitly converts the ~pointer into a borrowed pointer when
  calling the function, but the same does not work when using the
  BytesReader struct. I think, I should use a make_bytes_reader
  function, but I didn't found one.

* String literals seem to be not immutable. Is that right. That means
  they are always heap allocated. I wished they were immutable, so
  that writing ~my string is stored in read-only memory.

  I never thought this would be possible:

  let s = ~my string;
  let mut s2 = s;
  s2[0] = 'c' as u8;

  Is there a way how a function which takes a ~str can state that it
  will not modify the content?

  In this regard I very much like the way the D language handles this.
  It uses const to state that it won't modify the value, while the
  value itself may be mutable. Then there is immutable, and a value
  declared as such will not change during the whole lifetime. 

  Of course in Rust, thanks to unique pointers, there is less need for
  immutability, as you cannot share a unique pointer between threads.

* Appending to strings. It's easy to push an element to an array by
  doing:

  let mut v: ~[int] = ~[1,2];
  v.push(3);
  v.push(4);

  But when I want to append to a string, I have to write:

  let mut s: ~str = ~;
  let mut s = str::append(s, abc);
  let mut s = str::append(s, def);

  I found this a bit counter-intuitive. I know there exists +=, but
  this will always create a new string. A  operator would be really
  nice to append to strings (or to arrays).

* Default initializers for structs. Would be nice to specify them like:

  struct S {a: int = 4, b: int = 3};

  I know I can use the .. notation, and this is very cool and more
  flexible, but I will have to type in a lot of code if the struct get
  pretty large.

  const DefaultS = S{a: 4, b: 3}; // imagine this has 100 fields :)
  let s = S{a: 4, ..DefaultS};

* Metaprogramming

  Given an arbitrary struct S {...} with some fields, it would be nice
  to somehow derive S.serialize and S.deserialize functions
  automatically. Are there any ideas how to do that? In C++ I use the
  preprocessor and templates for that. In D, thanks to
  compile-time-code-evaluation, I can write code that will introspect
  the struct during compile-time and then generate code.

  I guess I could write a macro like:

  define_ser_struct!(S, field1, int, field2, uint, ...)

  which would generate the struct S and two functions for
  serialization. Would that be possible with macros?

Thanks in advance,

  Michael


___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions and ideas

2012-12-23 Thread Patrick Walton

On 12/23/12 4:25 PM, Michael Neumann wrote:

What is the big advantage of having a tracing GC over ref counting?
With GC we'd get rid of the extra indirection and extra operations
during aliasing, so it's basically a performance issue, right?


Yes, and we might also be able to allow temporary parallel access to 
GC'd data for pure functions only. If we can root the data in one task 
then we can potentially temporarily operate on @ data in parallel. 
Unfortunately that's hard to do for reference counted data, because our 
reference counts are not thread-safe for performance reasons.



Actually I was thinking of sth like in Ruby:

   Array.new(size=10) {|i| i % 2}

gives:

   [0, 1, 0, 1, 0, 1, 0, 1...]


Use `vec::from_fn`. For example:

rusti do vec::from_fn(10) |i| { i % 2 }
~[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]


Is this an issue the LLVM developers are working on?


It's more of a Rust-specific thing. `rustc` should be providing more 
hints to LLVM so that it does the right thing. I think that, to a first 
approximation, any higher-order function in Rust would benefit from 
`always_inline` or at least `inlinehint`.


LLVM's built-in inlining heuristics are more tuned to C++; I don't think 
that's likely to change, given upstream LLVM's goals. Maybe if C++11 
uses more higher-order functions. In any case, this should be something 
fixable in `rustc` itself; I don't think LLVM will need to change much 
if any.



Yes, I think sth with copy in the name would be less surprising. Ok,
unwrap makes sense. Or maybe get() and get_copy()?


Yeah, `get_copy` sounds reasonable to me.


So a const function (in terms of C++ ;-) would always take a str
pointer? Makes absolute sense to me.


Yes. You can temporarily borrow a ~str as immutable.


Ideally there would be an operator, as writing .append() all the time is
quite tedious.


I think `+=` will have this signature:

trait AddAssignRhs,Result {
fn add_assign(mut self, other: Rhs) - Result;
}

Which would allow += to work as you suggest.


Hm, this is interesting. Is there somewhere a simple example how to use
#[auto_encode] and what my msgpack library needs to implement to work
with it?


Unfortunately I'm not an expert on `auto_encode`. Erick Tryzelaar, Brian 
Anderson, or Graydon may know better.



I see. So it would be possible to write a syntax extension called for
example iter_fields!(struct_Type) which could be used to generate i.e.
custom serializers. But that would be probably similar to #auto_encode,
just that it could be more user-defined.


Well, you'd need to write it on the struct definition, so that it has 
access to the struct contents (as syntax extensions run before 
typechecking).


Actually, #[auto_encode] is a syntax extension itself, as some syntax 
extensions can be written using #[] attribute notation. This should 
eventually extend to user-defined syntax extensions as well.


Patrick

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions and ideas

2012-12-23 Thread Michael Neumann
Am Sun, 23 Dec 2012 12:20:07 -0500
schrieb Patrick Walton pwal...@mozilla.com:

 On 12/23/12 10:43 AM, Michael Neumann wrote:
  Hi,

 [...]
 
  * I do not fully understand the warning of the following script:
 
 fn main() {
   let bytes =
 io::read_whole_file(path::Path(/tmp/matching.msgpack)).get();
 }
 
 t2.rs:2:14: 2:78 warning: instantiating copy type parameter with
  a not implicitly copyable type t2.rs:2   let bytes =
 io::read_whole_file(path::Path(/tmp/matching.msgpack)).get();
 ^~~~
 
 Does it mean that it will copy the ~str again? When I use pattern
 matching instead of get(), I don't get this warning, but it
  seems to be slower. Will it just silence the warning???
 
 Yes, it means it will copy the string again. To avoid this, you want 
 result::unwrap() or option::unwrap() instead. I've been thinking for 
 some time that .unwrap() should change to .get() and .get() should 
 change to .copy_value() or something.

That's strange. If I use result::unwrap() it is consistently becoming
much slower! But the warning goes away. While get() is faster, but
there is this warning. Btw, there is also no .unwrap(), just
result::unwrap(). I believe that unwrap() is copying, while get() is
passing a reference somehow.

Michael
___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev


Re: [rust-dev] Misc questions and ideas

2012-12-23 Thread Patrick Walton

On 12/23/12 5:53 PM, Michael Neumann wrote:

That's strange. If I use result::unwrap() it is consistently becoming
much slower! But the warning goes away. While get() is faster, but
there is this warning. Btw, there is also no .unwrap(), just
result::unwrap(). I believe that unwrap() is copying, while get() is
passing a reference somehow.


This is not what I see. This program:

fn f() - Result~str,~str {
Ok(~hello world)
}

fn main() {
for uint::range(0, 0x1234567) |_| {
let _ = f().get();
}
}

Has this performance:

real0m15.991s
user0m15.899s
sys 0m0.016s

While this program:

fn f() - Result~str,~str {
Ok(~hello world)
}

fn main() {
for uint::range(0, 0x1234567) |_| {
let _ = result::unwrap(f());
}
}

Has this performance:

real0m4.318s
user0m4.255s
sys 0m0.013s

Patrick

___
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev