Re: [rust-dev] Misc questions
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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