unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* Unbuffered socket I/O
@ 2007-02-23 17:09 Ludovic Courtès
  2007-02-23 22:36 ` Neil Jerram
  2007-02-25 22:57 ` Kevin Ryde
  0 siblings, 2 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-02-23 17:09 UTC (permalink / raw)
  To: Guile-Devel

Hi,

Is there a reason why `SCM_SOCK_FD_TO_PORT ()' in `socket.c' asks for an
unbuffered port?  This results in awful inefficiency, as can be seen
with `strace':

  read(7, "\x43", 1)                      = 1
  select(1024, [7], [], [], {0, 0})       = 1 (in [7], left {0, 0})
  read(7, "\x4c", 1)                      = 1
  select(1024, [7], [], [], {0, 0})       = 1 (in [7], left {0, 0})
  read(7, "\x41", 1)                      = 1
  select(1024, [7], [], [], {0, 0})       = 1 (in [7], left {0, 0})

I'd like to change it to:

  #define SCM_SOCK_FD_TO_PORT(fd) \
     scm_fdes_to_port (fd, "r", sym_socket)

Thanks,
Ludovic.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-23 17:09 Unbuffered socket I/O Ludovic Courtès
@ 2007-02-23 22:36 ` Neil Jerram
  2007-02-25 22:57 ` Kevin Ryde
  1 sibling, 0 replies; 21+ messages in thread
From: Neil Jerram @ 2007-02-23 22:36 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:

> Hi,
>
> Is there a reason why `SCM_SOCK_FD_TO_PORT ()' in `socket.c' asks for an
> unbuffered port?  This results in awful inefficiency, as can be seen
> with `strace':

I don't know the details, but I recall people in the past talking
about Guile doing its own buffering.  Does that help at all?

Regards,
     Neil



_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-23 17:09 Unbuffered socket I/O Ludovic Courtès
  2007-02-23 22:36 ` Neil Jerram
@ 2007-02-25 22:57 ` Kevin Ryde
  2007-02-26 14:07   ` Ludovic Courtès
  1 sibling, 1 reply; 21+ messages in thread
From: Kevin Ryde @ 2007-02-25 22:57 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> Is there a reason why `SCM_SOCK_FD_TO_PORT ()' in `socket.c' asks for an
> unbuffered port?

To make send and receive conversations reliable, according to sockets
section in the manual.  (I don't think I added that.  I hope I'm not
claiming my own words as an authority!)

Also of course send and recv! don't use the buffering, so there's no
point having it when working with packets.

>   #define SCM_SOCK_FD_TO_PORT(fd) \
>      scm_fdes_to_port (fd, "r", sym_socket)

That's an incompatible change, I think, since it can leave unflushed
data where previously it went straight out.

Perhaps the unbuffering can be reiterated in the manual or docstrings
in each of socket, socketpair and accept which create such ports.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-25 22:57 ` Kevin Ryde
@ 2007-02-26 14:07   ` Ludovic Courtès
  2007-02-26 20:32     ` Neil Jerram
  2007-02-26 23:27     ` Kevin Ryde
  0 siblings, 2 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-02-26 14:07 UTC (permalink / raw)
  To: Guile-Devel

[-- Attachment #1: Type: text/plain, Size: 1489 bytes --]

Hi,

Kevin Ryde <user42@zip.com.au> writes:

> ludovic.courtes@laas.fr (Ludovic Courtès) writes:

>>   #define SCM_SOCK_FD_TO_PORT(fd) \
>>      scm_fdes_to_port (fd, "r", sym_socket)
>
> That's an incompatible change, I think, since it can leave unflushed
> data where previously it went straight out.

Right.  That's an incompatible change if the _output_ is buffered.
Input can be buffered, though, without this being visible by users.

Fortunately, port buffering doesn't have to be symmetrical (although the
API allowing to do that is internal---actually, we might want to expose
and document `scm_fport_buffer_add ()').  Thus, I propose the following
change, where sockets are turned into ports whose output is left
unbuffered and whose input is buffered.  Again, this has no visible
effect on user programs AFAICS.

I tried it with TCP server-side sockets and the performance impact is
_huge_.  Actually, it seems even hard to write a TCP server without this
because it takes so long to read a single octet that clients may
time-out before the server is done receiving their request! (I observed
this with RPC server/clients)

BTW, do you know what the purpose of `fport_wait_for_input ()' is?  It
does nothing for O_NONBLOCK streams and waits for events otherwise.
Since, for blocking streams, `read ()' does not return until either EOF
is reached or at least one octet was read, `fport_wait_for_input ()'
seems redundant.

Thanks,
Ludovic.



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Patch providing socket input buffering. --]
[-- Type: text/x-patch, Size: 3183 bytes --]

--- orig/libguile/fports.c
+++ mod/libguile/fports.c
@@ -90,11 +90,11 @@
 /* default buffer size, used if the O/S won't supply a value.  */
 static const size_t default_buffer_size = 1024;
 
-/* create FPORT buffer with specified sizes (or -1 to use default size or
-   0 for no buffer.  */
-static void
-scm_fport_buffer_add (SCM port, long read_size, int write_size)
-#define FUNC_NAME "scm_fport_buffer_add"
+/* Create FPORT buffer with specified sizes (or -1 to use default size or
+   0 for no buffer).  */
+void
+scm_i_fport_buffer_add (SCM port, long read_size, long write_size)
+#define FUNC_NAME "scm_i_fport_buffer_add"
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
 
@@ -212,7 +212,7 @@
   if (pt->write_buf != &pt->shortbuf)
     scm_gc_free (pt->write_buf, pt->write_buf_size, "port buffer");
 
-  scm_fport_buffer_add (port, csize, csize);
+  scm_i_fport_buffer_add (port, csize, csize);
   return SCM_UNSPECIFIED;
 }
 #undef FUNC_NAME
@@ -465,9 +465,9 @@
     pt->rw_random = SCM_FDES_RANDOM_P (fdes);
     SCM_SETSTREAM (port, fp);
     if (mode_bits & SCM_BUF0)
-      scm_fport_buffer_add (port, 0, 0);
+      scm_i_fport_buffer_add (port, 0, 0);
     else
-      scm_fport_buffer_add (port, -1, -1);
+      scm_i_fport_buffer_add (port, -1, -1);
   }
   SCM_SET_FILENAME (port, name);
   scm_i_pthread_mutex_unlock (&scm_i_port_table_mutex);


--- orig/libguile/fports.h
+++ mod/libguile/fports.h
@@ -60,7 +60,8 @@
 SCM_API SCM scm_i_fdes_to_port (int fdes, long mode_bits, SCM name);
 SCM_API int scm_i_fport_truncate (SCM, SCM);
 SCM_API SCM scm_i_fport_seek (SCM, SCM, int);
-
+SCM_API void scm_i_fport_buffer_add (SCM port, long read_size,
+				     long write_size);
 
 #endif  /* SCM_FPORTS_H */
 


--- orig/libguile/socket.c
+++ mod/libguile/socket.c
@@ -403,9 +403,26 @@
 
 #endif  /* HAVE_IPV6 */
 
+\f
+/* Sockets.  */
+
 SCM_SYMBOL (sym_socket, "socket");
 
-#define SCM_SOCK_FD_TO_PORT(fd) scm_fdes_to_port (fd, "r+0", sym_socket)
+/* Size of the input buffer of socket ports.  */
+#define SCM_SOCKET_INPUT_BUFFER_SIZE  4096
+
+
+/* Return a socket with buffered input and unbufferred output.  */
+static inline SCM
+socket_to_port (int fd)
+{
+  SCM port;
+
+  port = scm_fdes_to_port (fd, "r+0", sym_socket);
+  scm_i_fport_buffer_add (port, SCM_SOCKET_INPUT_BUFFER_SIZE, 0);
+
+  return port;
+}
 
 SCM_DEFINE (scm_socket, "socket", 3, 0, 0,
             (SCM family, SCM style, SCM proto),
@@ -429,7 +446,7 @@
 	       scm_to_int (proto));
   if (fd == -1)
     SCM_SYSERROR;
-  return SCM_SOCK_FD_TO_PORT (fd);
+  return socket_to_port (fd);
 }
 #undef FUNC_NAME
 
@@ -451,7 +468,7 @@
   if (socketpair (fam, scm_to_int (style), scm_to_int (proto), fd) == -1)
     SCM_SYSERROR;
 
-  return scm_cons (SCM_SOCK_FD_TO_PORT (fd[0]), SCM_SOCK_FD_TO_PORT (fd[1]));
+  return scm_cons (socket_to_port (fd[0]), socket_to_port (fd[1]));
 }
 #undef FUNC_NAME
 #endif
@@ -1312,7 +1329,7 @@
   newfd = accept (fd, addr, &addr_size);
   if (newfd == -1)
     SCM_SYSERROR;
-  newsock = SCM_SOCK_FD_TO_PORT (newfd);
+  newsock = socket_to_port (newfd);
   address = _scm_from_sockaddr (addr, addr_size, FUNC_NAME);
   return scm_cons (newsock, address);
 }




[-- Attachment #3: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-26 14:07   ` Ludovic Courtès
@ 2007-02-26 20:32     ` Neil Jerram
  2007-02-26 23:35       ` Kevin Ryde
  2007-02-27  9:11       ` Ludovic Courtès
  2007-02-26 23:27     ` Kevin Ryde
  1 sibling, 2 replies; 21+ messages in thread
From: Neil Jerram @ 2007-02-26 20:32 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:

> I tried it with TCP server-side sockets and the performance impact is
> _huge_.  Actually, it seems even hard to write a TCP server without this
> because it takes so long to read a single octet that clients may
> time-out before the server is done receiving their request! (I observed
> this with RPC server/clients)

I have no technical objection to your patch, but I'm afraid you must
be missing something, or else there is something special about the
environment that you were measuring in.  I have played with a few
TCP-based Guile applications (including the Emacs debugging interface)
and not noticed any obvious performance problem.

Regards,
     Neil



_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-26 14:07   ` Ludovic Courtès
  2007-02-26 20:32     ` Neil Jerram
@ 2007-02-26 23:27     ` Kevin Ryde
  2007-02-28  9:47       ` Ludovic Courtès
  2007-05-13 17:13       ` `scm_std_select ()' call in `fport_wait_for_input ()' Ludovic Courtès
  1 sibling, 2 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-02-26 23:27 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> Right.  That's an incompatible change if the _output_ is buffered.
> Input can be buffered, though, without this being visible by users.

Alas, that too is in an incompatible change, because recv! ignores
buffering.

> Fortunately, port buffering doesn't have to be symmetrical (although the
> API allowing to do that is internal---actually, we might want to expose
> and document `scm_fport_buffer_add ()').

Sounds good.  Perhaps `setvbuf-input' and `setvbuf-output' for the two
directions.  They could go in 1.8 too if you're careful with the
implementation.

Might have to check the SCM_BUFLINE and SCM_BUF0 flags are ok though.
Suspect the answer is yes, SCM_BUFLINE being an output side feature,
and SCM_BUF0 already merely a combination read==unbuf + write==unbuf.

> Thus, I propose the following
> change, where sockets are turned into ports whose output is left
> unbuffered and whose input is buffered.

The manual could emphasise that unbuffered is not what you want if
reading piecemeal, but the default should stay as advertised.

> BTW, do you know what the purpose of `fport_wait_for_input ()' is?

Maybe left from the 1.6 cooperative threads.

> It
> does nothing for O_NONBLOCK streams and waits for events otherwise.
> Since, for blocking streams, `read ()' does not return until either EOF
> is reached or at least one octet was read, `fport_wait_for_input ()'
> seems redundant.

Ahh, hang on, I wonder if it's a hack to do an "exit guile" while
blocked within a read(), thus allowing gc to run in other threads.

If that's true then presumably the write side is afflicted too, as
well as various other potentially blocking operations like
read-string!/partial and gethost.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-26 20:32     ` Neil Jerram
@ 2007-02-26 23:35       ` Kevin Ryde
  2007-02-27  9:11       ` Ludovic Courtès
  1 sibling, 0 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-02-26 23:35 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

Neil Jerram <neil@ossau.uklinux.net> writes:
>
> I have played with a few
> TCP-based Guile applications (including the Emacs debugging interface)
> and not noticed any obvious performance problem.

Likewise.  I've been reading http headers byte by byte - because I was
too lazy to do it properly yet - with no obvious pain.  I read the
bodies block by block though, with read-string!/partial.

Dunno if read-string!/partial is entirely obvious from what's in the
manual.  Maybe the "Reading" node should have some prominent cross
refs to the rdelim and rw modules -- don't want people thinking
read-char is the right way to go.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-26 20:32     ` Neil Jerram
  2007-02-26 23:35       ` Kevin Ryde
@ 2007-02-27  9:11       ` Ludovic Courtès
  2007-02-28 21:24         ` Kevin Ryde
  1 sibling, 1 reply; 21+ messages in thread
From: Ludovic Courtès @ 2007-02-27  9:11 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

[-- Attachment #1: Type: text/plain, Size: 661 bytes --]

Hi,

Neil Jerram <neil@ossau.uklinux.net> writes:

> I have no technical objection to your patch, but I'm afraid you must
> be missing something, or else there is something special about the
> environment that you were measuring in.  I have played with a few
> TCP-based Guile applications (including the Emacs debugging interface)
> and not noticed any obvious performance problem.

I tried the attached script, both with and without the patch.  Here, it
says "time: 7" with the patch and "time: 2645" without.

Now, it seems that I was observing an even higher difference because the
Guile server script was running under `strace'...  :-)

Thanks,
Ludovic.



[-- Attachment #2: Test program. --]
[-- Type: text/plain, Size: 944 bytes --]

(use-modules (srfi srfi-4))

(define %port 7778)
(define %size 5000000)

(define (time thunk)
  (let ((start (get-internal-run-time)))
    (thunk)
    (let ((end (get-internal-run-time)))
      (format #t "time: ~a~%" (- end start)))))

(if (= 0 (primitive-fork))

    (let ((s (socket PF_INET SOCK_STREAM 0)))
      (bind s AF_INET INADDR_LOOPBACK %port)
      (listen s 1024)

      (format #t "accepting connections...~%")
      (let* ((accepted (accept s))
             (port (car accepted))
             (vec  (make-u8vector %size)))
        (time
         (lambda ()
           (uniform-vector-read! vec port)))
        (close-port port)))

    (let ((s (socket PF_INET SOCK_STREAM 0))
          (vec (make-u8vector %size)))
      (format #t "client~%")
      (sleep 1)
      (connect s AF_INET INADDR_LOOPBACK %port)

      (format #t "writing...~%")
      (uniform-vector-write vec s)
      (format #t "done.~%")
      (close-port s)))

[-- Attachment #3: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-26 23:27     ` Kevin Ryde
@ 2007-02-28  9:47       ` Ludovic Courtès
  2007-02-28 20:59         ` Kevin Ryde
  2007-05-13 17:13       ` `scm_std_select ()' call in `fport_wait_for_input ()' Ludovic Courtès
  1 sibling, 1 reply; 21+ messages in thread
From: Ludovic Courtès @ 2007-02-28  9:47 UTC (permalink / raw)
  To: Guile-Devel

[-- Attachment #1: Type: text/plain, Size: 1851 bytes --]

Hi,

Kevin Ryde <user42@zip.com.au> writes:

> ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>>
>> Right.  That's an incompatible change if the _output_ is buffered.
>> Input can be buffered, though, without this being visible by users.
>
> Alas, that too is in an incompatible change, because recv! ignores
> buffering.

Hmm, right.

> Sounds good.  Perhaps `setvbuf-input' and `setvbuf-output' for the two
> directions.  They could go in 1.8 too if you're careful with the
> implementation.

Below is a tentative patch (I chose more meaningful names since they
don't relate to `setvbuf(3)').  The issue is that it duplicates fport
buffering logic, but I couldn't think of a nice way to factorize this
with, e.g., `scm_fport_buffer_add ()'.  What do you think?

Besides, how about applying the change I originally proposed to HEAD?
That would require changing `recv!', `send', etc. so that they are
somehow "buffering-aware".  Ideally, it would be nice if they didn't
break through the port abstraction as they currently do, but I don't
know how that could be implemented (what should be the meaning of
`MSG_DONTROUTE' on an input string buffer?  ;-)).

>> It
>> does nothing for O_NONBLOCK streams and waits for events otherwise.
>> Since, for blocking streams, `read ()' does not return until either EOF
>> is reached or at least one octet was read, `fport_wait_for_input ()'
>> seems redundant.
>
> Ahh, hang on, I wonder if it's a hack to do an "exit guile" while
> blocked within a read(), thus allowing gc to run in other threads.

Indeed, `scm_std_select ()' does a "leave guile".

> If that's true then presumably the write side is afflicted too, as
> well as various other potentially blocking operations like
> read-string!/partial and gethost.

Afflicted by what?

Thanks,
Ludovic.



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Asymmetrical fport buffering. --]
[-- Type: text/x-patch, Size: 4273 bytes --]

--- orig/libguile/fports.c
+++ mod/libguile/fports.c
@@ -90,10 +90,10 @@
 /* default buffer size, used if the O/S won't supply a value.  */
 static const size_t default_buffer_size = 1024;
 
-/* create FPORT buffer with specified sizes (or -1 to use default size or
-   0 for no buffer.  */
+/* Create FPORT buffer with specified sizes (or -1 to use default size or
+   0 for no buffer).  */
 static void
-scm_fport_buffer_add (SCM port, long read_size, int write_size)
+scm_fport_buffer_add (SCM port, long read_size, long write_size)
 #define FUNC_NAME "scm_fport_buffer_add"
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
@@ -217,6 +217,115 @@
 }
 #undef FUNC_NAME
 
+SCM_DEFINE (scm_set_port_input_buffer_size_x, "set-port-input-buffer-size!",
+	    2, 0, 0,
+	    (SCM port, SCM size),
+	    "Use a @var{size}-octet input buffer for @var{port}, which "
+	    "must be a file or socket port.  The current input buffer "
+	    "of @var{port} must contain less than @var{size} octets.")
+#define FUNC_NAME s_scm_set_port_input_buffer_size_x
+{
+  scm_t_port *pt;
+  size_t c_size;
+
+  SCM_VALIDATE_FPORT (1, port);
+  c_size = scm_to_uint (size);
+
+  pt = SCM_PTAB_ENTRY (port);
+
+  if (pt->read_buf_size != c_size)
+    {
+      size_t c_offset, c_end;
+      unsigned char *new_buf;
+
+      c_end = pt->read_end - pt->read_buf;
+      c_offset = pt->read_pos - pt->read_buf;
+
+      if (c_offset > c_size)
+	scm_wrong_type_arg_msg (FUNC_NAME, 1, port,
+				"Input buffer must be flushed before it "
+				"can be shrunk");
+
+      if (c_size == 0)
+	new_buf = &pt->shortbuf, c_size = 1;
+      else
+	{
+	  if (pt->read_buf != &pt->shortbuf)
+	    new_buf = scm_gc_realloc (pt->read_buf,
+				      pt->read_buf_size, c_size,
+				      "port buffer");
+	  else
+	    new_buf = scm_gc_malloc (c_size, "port buffer");
+	}
+
+      pt->read_buf = new_buf;
+      pt->read_end = new_buf + c_end;
+      pt->read_pos = new_buf + c_offset;
+      pt->read_buf_size = c_size;
+
+      if ((pt->read_buf_size == 0) && (pt->write_buf_size == 0))
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+    }
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_set_port_output_buffer_size_x, "set-port-output-buffer-size!",
+	    2, 0, 0,
+	    (SCM port, SCM size),
+	    "Use a @var{size}-octet output buffer for @var{port}, which "
+	    "must be a file or socket port.  The current output buffer of "
+	    "@var{port} must contain less than @var{size} octets.")
+#define FUNC_NAME s_scm_set_port_output_buffer_size_x
+{
+  scm_t_port *pt;
+  size_t c_size;
+
+  SCM_VALIDATE_FPORT (1, port);
+  c_size = scm_to_uint (size);
+
+  pt = SCM_PTAB_ENTRY (port);
+
+  if (pt->write_buf_size != c_size)
+    {
+      size_t c_offset, c_end;
+      unsigned char *new_buf;
+
+      c_end = pt->write_end - pt->write_buf;
+      c_offset = pt->write_pos - pt->write_buf;
+
+      if (c_offset > c_size)
+	scm_wrong_type_arg_msg (FUNC_NAME, 1, port,
+				"Output buffer must be flushed before it "
+				"can be shrunk");
+
+      if (c_size == 0)
+	new_buf = &pt->shortbuf, c_size = 1;
+      else
+	{
+	  if (pt->write_buf != &pt->shortbuf)
+	    new_buf = scm_gc_realloc (pt->write_buf,
+				      pt->write_buf_size, c_size,
+				      "port buffer");
+	  else
+	    new_buf = scm_gc_malloc (c_size, "port buffer");
+	}
+
+      pt->write_buf = new_buf;
+      pt->write_end = new_buf + c_end;
+      pt->write_pos = new_buf + c_offset;
+      pt->write_buf_size = c_size;
+
+      if ((pt->read_buf_size == 0) && (pt->write_buf_size == 0))
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+    }
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+
 /* Move ports with the specified file descriptor to new descriptors,
  * resetting the revealed count to 0.
  */


--- orig/libguile/fports.h
+++ mod/libguile/fports.h
@@ -49,6 +49,8 @@
 \f
 SCM_API SCM scm_setbuf0 (SCM port);
 SCM_API SCM scm_setvbuf (SCM port, SCM mode, SCM size);
+SCM_API SCM scm_set_port_input_buffer_size_x (SCM port, SCM size);
+SCM_API SCM scm_set_port_output_buffer_size_x (SCM port, SCM size);
 SCM_API void scm_evict_ports (int fd);
 SCM_API SCM scm_open_file (SCM filename, SCM modes);
 SCM_API SCM scm_fdes_to_port (int fdes, char *mode, SCM name);




[-- Attachment #3: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-28  9:47       ` Ludovic Courtès
@ 2007-02-28 20:59         ` Kevin Ryde
  2007-03-01 10:44           ` Ludovic Courtès
  0 siblings, 1 reply; 21+ messages in thread
From: Kevin Ryde @ 2007-02-28 20:59 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> Below is a tentative patch (I chose more meaningful names since they
> don't relate to `setvbuf(3)').

Call them setvbuf.  Two names for the same thing is more confusing
than one (slightly obscure) name.

> Besides, how about applying the change I originally proposed to HEAD?

No, principally because unbuffered is an advertised feature.  Even if
it was year zero you'd probably still choose them to start unbuffered,
since there's various ways to do comms and it can be up to an
application how much might be desirable.  For block operations for
instance it's certainly not wanted.

> That would require changing `recv!', `send', etc. so that they are
> somehow "buffering-aware".

That could be ok.  I doubt it makes much sense, because buffering is
more or less stream oriented yet those funcs are about packets.  But
they could still look at the buffers in the interests of keeping data
in sequence.

> Afflicted by what?

Blocking in one thread prevents gc in any other thread, bringing
everything to a screeching halt, before long.


> +	    "Use a @var{size}-octet

Say "byte" not "octet".  Octet sounds like one of those dreadful CCITT
standards.

> +  size_t c_size;
> +  c_size = scm_to_uint (size);

There's an scm_to_size_t for that.

+      if (c_offset > c_size)
+	scm_wrong_type_arg_msg (FUNC_NAME, 1, port,
+				"Input buffer must be flushed before it "
+				"can be shrunk");

The current code silently discards, I think that's probably enough.
If it was up to me I might move it to the putback buffer though.

> +      if (c_size == 0)
> +	new_buf = &pt->shortbuf, c_size = 1;

I suppose an explicit request for size==1 could use the shortbuf too,
though I see the existing code doesn't do that.  (Maybe there's a
reason it doesn't.)

I expect there's a memory leak there if you don't free the old buf
when becoming unbuffered.

> +      if ((pt->read_buf_size == 0) && (pt->write_buf_size == 0))
> +	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);

That'd need to turn the flag off too, if it's become non-zero.

> +      if (c_offset > c_size)
> +	scm_wrong_type_arg_msg (FUNC_NAME, 1, port,
> +				"Output buffer must be flushed before it "
> +				"can be shrunk");

That doesn't need to be an error does it?  Can't it be flushed there
and then?

Otherwise looks ok as long as it actually works! :)


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-27  9:11       ` Ludovic Courtès
@ 2007-02-28 21:24         ` Kevin Ryde
  2007-03-01  9:10           ` Ludovic Courtès
  0 siblings, 1 reply; 21+ messages in thread
From: Kevin Ryde @ 2007-02-28 21:24 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
>            (uniform-vector-read! vec port)))

Slackness in the implementation of that function.  It knows a size to
read and should come out as a read() system call of that size.  Yet it
always goes through the port buffer.  You need to post the problem
instead of leaping to a "solution".

read-string!/partial is much better in that respect.  Maybe that func
should be loosened up to work on non-fports too, that could be nice.

It's a bit of a failing of the ptab port functions that there's no
"read N bytes", but that needn't prevent uniform-vector-read! doing
the right thing on fports.

Could think about whether it ought to make multiple calls to fill the
requested size.  I guess that's how it is now, in effect, so probably
yes.  Could also think if those calls should then cope with O_NONBLOCK
too, by sleeping in select() if finding there's nothing to read yet.
On a single read call I would say let it throw an error, but if the
aim is to block until the requested size then it's easy enough to
cope.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-28 21:24         ` Kevin Ryde
@ 2007-03-01  9:10           ` Ludovic Courtès
  2007-03-04 23:48             ` Kevin Ryde
                               ` (2 more replies)
  0 siblings, 3 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-03-01  9:10 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

Hi,

Kevin Ryde <user42@zip.com.au> writes:

> ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>>
>>            (uniform-vector-read! vec port)))
>
> Slackness in the implementation of that function.  It knows a size to
> read and should come out as a read() system call of that size.  Yet it
> always goes through the port buffer.

I believe it's a feature, not some "slackness".  ;-)

The function is meant to read from _any_ kind of input port.  Thus, it
can definitely not use the `read ()' system call to that end.  Instead,
it rightfully uses one of the two public functions available to read
from an arbitrary port: `scm_getc ()' and `scm_c_read ()'.

> read-string!/partial is much better in that respect.  Maybe that func
> should be loosened up to work on non-fports too, that could be nice.
>
> It's a bit of a failing of the ptab port functions that there's no
> "read N bytes", but that needn't prevent uniform-vector-read! doing
> the right thing on fports.

There's `scm_c_read ()' for "read N bytes", but it translates into N
calls to `scm_getc ()'.  If the port is unbuffered, then each invocation
of `scm_getc ()' translates in one `scm_fill_input ()' call, which in
turn translates in one `select ()' and one `read ()' of size 1.  I think
_this_ is the issue, not the fact that `uniform-vector-read!' can read
from any kind of port.

IMO it feels wrong to dismiss the port abstraction (e.g., by testing for
fports and using fport-specific I/O methods) just to work around this
performance problem.

> Could also think if those calls should then cope with O_NONBLOCK
> too, by sleeping in select() if finding there's nothing to read yet.
> On a single read call I would say let it throw an error, but if the
> aim is to block until the requested size then it's easy enough to
> cope.

I noticed that `fport_fill_input ()' would raise an error when reading
from a non-blocking file descriptor where input is not available (i.e.,
when `read ()' returns -1 with ERRNO == EWOULDBLOCK).  Thus it must be
inconvenient (at best) to use the port I/O functions on a port whose
underlying FD is in non-blocking mode.  I don't know what would need to
be done to fix this.

Thanks,
Ludovic.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-02-28 20:59         ` Kevin Ryde
@ 2007-03-01 10:44           ` Ludovic Courtès
  2007-03-05 23:15             ` Kevin Ryde
  0 siblings, 1 reply; 21+ messages in thread
From: Ludovic Courtès @ 2007-03-01 10:44 UTC (permalink / raw)
  To: Guile-Devel

[-- Attachment #1: Type: text/plain, Size: 1085 bytes --]

Hi,

Attached is an updated patch.  Ok to apply?

Kevin Ryde <user42@zip.com.au> writes:

>> Besides, how about applying the change I originally proposed to HEAD?
>
> No, principally because unbuffered is an advertised feature.

Hey, it's only advertised as long as we advertise it!  ;-)

I mean, if we start nitpicking about such changes in HEAD, then we'll
never implement a Unicode-capable port I/O interface, will we?  :-)

> Even if
> it was year zero you'd probably still choose them to start unbuffered,
> since there's various ways to do comms and it can be up to an
> application how much might be desirable.  For block operations for
> instance it's certainly not wanted.

The thing is that port buffering in Guile does not necessarily have a
direct relationship to OS-level buffering.  This is clear for input,
where one can have unbuffered OS-level file descriptor and where
port-level input buffering only impacts performance, not semantics.
Conversely, port-level output buffering does have a visible impact from
the outside world.

Thanks for your comments!

Ludovic.



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: The improved patch. --]
[-- Type: text/x-patch, Size: 8079 bytes --]

--- orig/doc/ref/posix.texi
+++ mod/doc/ref/posix.texi
@@ -472,6 +472,21 @@
 @end defvar
 @end deffn
 
+@deffn {Scheme Procedure} setvbuf-input port size
+@deffnx {C Function} scm_setvbuf_input (port, size)
+Use a @var{size}-byte input buffer for @var{port}, which must be a
+file or socket port.  Note that buffered data is lost if the input
+buffer contains more than @var{size} bytes.  Thus, it may be necessary
+to call @code{drain-input} prior to @code{setvbuf-input}.
+@end deffn
+
+@deffn {Scheme Procedure} setvbuf-output port size
+@deffnx {C Function} scm_setvbuf_output (port, size)
+Use a @var{size}-byte output buffer for @var{port}, which must be a
+file or socket port.  The current output buffer may be flushed as a
+result of this operation.
+@end deffn
+
 @deffn {Scheme Procedure} fcntl port/fd cmd [value]
 @deffnx {C Function} scm_fcntl (object, cmd, value)
 Apply @var{cmd} on @var{port/fd}, either a port or file descriptor.


--- orig/libguile/fports.c
+++ mod/libguile/fports.c
@@ -86,14 +86,20 @@
 
 scm_t_bits scm_tc16_fport;
 
+static void fport_flush (SCM port);
+
+/* Hints passed to the GC allocation functions for port buffers and file
+   ports.  */
+static const char gc_port_buffer_hint[] = "port buffer";
+static const char gc_file_port_hint[]   = "file port";
 
 /* default buffer size, used if the O/S won't supply a value.  */
 static const size_t default_buffer_size = 1024;
 
-/* create FPORT buffer with specified sizes (or -1 to use default size or
-   0 for no buffer.  */
+/* Create FPORT buffer with specified sizes (or -1 to use default size or
+   0 for no buffer).  */
 static void
-scm_fport_buffer_add (SCM port, long read_size, int write_size)
+scm_fport_buffer_add (SCM port, long read_size, long write_size)
 #define FUNC_NAME "scm_fport_buffer_add"
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
@@ -118,7 +124,7 @@
 
   if (SCM_INPUT_PORT_P (port) && read_size > 0)
     {
-      pt->read_buf = scm_gc_malloc (read_size, "port buffer");
+      pt->read_buf = scm_gc_malloc (read_size, gc_port_buffer_hint);
       pt->read_pos = pt->read_end = pt->read_buf;
       pt->read_buf_size = read_size;
     }
@@ -130,7 +136,7 @@
 
   if (SCM_OUTPUT_PORT_P (port) && write_size > 0)
     {
-      pt->write_buf = scm_gc_malloc (write_size, "port buffer");
+      pt->write_buf = scm_gc_malloc (write_size, gc_port_buffer_hint);
       pt->write_pos = pt->write_buf;
       pt->write_buf_size = write_size;
     }
@@ -208,15 +214,140 @@
       pt->read_buf_size = pt->saved_read_buf_size;
     }
   if (pt->read_buf != &pt->shortbuf)
-    scm_gc_free (pt->read_buf, pt->read_buf_size, "port buffer");
+    scm_gc_free (pt->read_buf, pt->read_buf_size, gc_port_buffer_hint);
   if (pt->write_buf != &pt->shortbuf)
-    scm_gc_free (pt->write_buf, pt->write_buf_size, "port buffer");
+    scm_gc_free (pt->write_buf, pt->write_buf_size, gc_port_buffer_hint);
 
   scm_fport_buffer_add (port, csize, csize);
   return SCM_UNSPECIFIED;
 }
 #undef FUNC_NAME
 
+SCM_DEFINE (scm_setvbuf_input, "setvbuf-input", 2, 0, 0,
+	    (SCM port, SCM size),
+	    "Use a @var{size}-byte input buffer for @var{port}, which "
+	    "must be a file or socket port.  Note that buffered data "
+	    "is lost if the input buffer contains more than @var{size} "
+	    "bytes.  Thus, it may be necessary to call "
+	    "@code{drain-input} prior to @code{setvbuf-input}.")
+#define FUNC_NAME s_scm_setvbuf_input
+{
+  scm_t_port *pt;
+  size_t c_size;
+
+  SCM_VALIDATE_FPORT (1, port);
+  c_size = scm_to_size_t (size);
+
+  pt = SCM_PTAB_ENTRY (port);
+
+  if (pt->read_buf_size != c_size)
+    {
+      size_t c_offset, c_end;
+      unsigned char *new_buf;
+
+      c_end = pt->read_end - pt->read_buf;
+      c_offset = pt->read_pos - pt->read_buf;
+
+      if (c_offset > c_size)
+	/* Discard all the buffered input.  */
+	c_offset = c_end = 0;
+
+      if (c_size == 0)
+	{
+	  new_buf = &pt->shortbuf, c_size = 1;
+	  if (pt->read_buf != &pt->shortbuf)
+	    scm_gc_free (pt->read_buf, pt->read_buf_size,
+			 gc_port_buffer_hint);
+	}
+      else
+	{
+	  if (pt->read_buf != &pt->shortbuf)
+	    new_buf = scm_gc_realloc (pt->read_buf,
+				      pt->read_buf_size, c_size,
+				      gc_port_buffer_hint);
+	  else
+	    new_buf = scm_gc_malloc (c_size, gc_port_buffer_hint);
+	}
+
+      pt->read_buf = new_buf;
+      pt->read_end = new_buf + c_end;
+      pt->read_pos = new_buf + c_offset;
+      pt->read_buf_size = c_size;
+
+      if ((pt->read_buf_size == 0) && (pt->write_buf_size == 0))
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+      else
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~SCM_BUF0);
+    }
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_setvbuf_output, "setvbuf-output", 2, 0, 0,
+	    (SCM port, SCM size),
+	    "Use a @var{size}-byte output buffer for @var{port}, which "
+	    "must be a file or socket port.  The current output buffer "
+	    "may be flushed as a result of this operation.")
+#define FUNC_NAME s_scm_setvbuf_output
+{
+  scm_t_port *pt;
+  size_t c_size;
+
+  SCM_VALIDATE_FPORT (1, port);
+  c_size = scm_to_size_t (size);
+
+  pt = SCM_PTAB_ENTRY (port);
+
+  if (pt->write_buf_size != c_size)
+    {
+      size_t c_offset, c_end;
+      unsigned char *new_buf;
+
+    do_it:
+      c_end = pt->write_end - pt->write_buf;
+      c_offset = pt->write_pos - pt->write_buf;
+
+      if (c_offset > c_size)
+	{
+	  /* Flush the output buffer so that it can be shrunk.  */
+	  fport_flush (port);
+	  goto do_it;
+	}
+
+      if (c_size == 0)
+	{
+	  new_buf = &pt->shortbuf, c_size = 1;
+	  if (pt->write_buf != &pt->shortbuf)
+	    scm_gc_free (pt->write_buf, pt->write_buf_size,
+			 gc_port_buffer_hint);
+	}
+      else
+	{
+	  if (pt->write_buf != &pt->shortbuf)
+	    new_buf = scm_gc_realloc (pt->write_buf,
+				      pt->write_buf_size, c_size,
+				      gc_port_buffer_hint);
+	  else
+	    new_buf = scm_gc_malloc (c_size, gc_port_buffer_hint);
+	}
+
+      pt->write_buf = new_buf;
+      pt->write_end = new_buf + c_end;
+      pt->write_pos = new_buf + c_offset;
+      pt->write_buf_size = c_size;
+
+      if ((pt->read_buf_size == 0) && (pt->write_buf_size == 0))
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+      else
+	SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~SCM_BUF0);
+    }
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+
 /* Move ports with the specified file descriptor to new descriptors,
  * resetting the revealed count to 0.
  */
@@ -459,7 +590,8 @@
   pt = SCM_PTAB_ENTRY(port);
   {
     scm_t_fport *fp
-      = (scm_t_fport *) scm_gc_malloc (sizeof (scm_t_fport), "file port");
+      = (scm_t_fport *) scm_gc_malloc (sizeof (scm_t_fport),
+				       gc_file_port_hint);
 
     fp->fdes = fdes;
     pt->rw_random = SCM_FDES_RANDOM_P (fdes);
@@ -583,8 +715,6 @@
 }
 #endif /* !__MINGW32__ */
 
-static void fport_flush (SCM port);
-
 /* fill a port's read-buffer with a single read.  returns the first
    char or EOF if end of file.  */
 static int
@@ -892,10 +1022,10 @@
   if (pt->read_buf == pt->putback_buf)
     pt->read_buf = pt->saved_read_buf;
   if (pt->read_buf != &pt->shortbuf)
-    scm_gc_free (pt->read_buf, pt->read_buf_size, "port buffer");
+    scm_gc_free (pt->read_buf, pt->read_buf_size, gc_port_buffer_hint);
   if (pt->write_buf != &pt->shortbuf)
-    scm_gc_free (pt->write_buf, pt->write_buf_size, "port buffer");
-  scm_gc_free (fp, sizeof (*fp), "file port");
+    scm_gc_free (pt->write_buf, pt->write_buf_size, gc_port_buffer_hint);
+  scm_gc_free (fp, sizeof (*fp), gc_file_port_hint);
   return rv;
 }
 


--- orig/libguile/fports.h
+++ mod/libguile/fports.h
@@ -49,6 +49,8 @@
 \f
 SCM_API SCM scm_setbuf0 (SCM port);
 SCM_API SCM scm_setvbuf (SCM port, SCM mode, SCM size);
+SCM_API SCM scm_setvbuf_input (SCM port, SCM size);
+SCM_API SCM scm_setvbuf_output (SCM port, SCM size);
 SCM_API void scm_evict_ports (int fd);
 SCM_API SCM scm_open_file (SCM filename, SCM modes);
 SCM_API SCM scm_fdes_to_port (int fdes, char *mode, SCM name);


[-- Attachment #3: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-01  9:10           ` Ludovic Courtès
@ 2007-03-04 23:48             ` Kevin Ryde
  2007-03-07  0:20             ` Kevin Ryde
  2007-03-07  0:30             ` Kevin Ryde
  2 siblings, 0 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-03-04 23:48 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> There's `scm_c_read ()' for "read N bytes", but it translates into N
> calls to `scm_getc ()'.

Then that's where it can best be fixed.

> IMO it feels wrong to dismiss the port abstraction

There's nothing super fantastic about the ptab funcs, there's
certainly no need to confine oneself to what's presently there.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-01 10:44           ` Ludovic Courtès
@ 2007-03-05 23:15             ` Kevin Ryde
  2007-08-07 16:01               ` Ludovic Courtès
  0 siblings, 1 reply; 21+ messages in thread
From: Kevin Ryde @ 2007-03-05 23:15 UTC (permalink / raw)
  To: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> +SCM_DEFINE (scm_setvbuf_output, "setvbuf-output", 2, 0, 0,
> +	    (SCM port, SCM size),

Can that take a mode, so it's possible to set _IOLBF ?

setvbuf-input could also take a mode, though _IOLBF wouldn't be wanted
there, but it'd make the args the same for all three funcs - the input
and output versions being the same as the main except only operating
on one side.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-01  9:10           ` Ludovic Courtès
  2007-03-04 23:48             ` Kevin Ryde
@ 2007-03-07  0:20             ` Kevin Ryde
  2007-03-07  0:30             ` Kevin Ryde
  2 siblings, 0 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-03-07  0:20 UTC (permalink / raw)
  To: guile-devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> of `scm_getc ()' translates in one `scm_fill_input ()' call, which in
> turn translates in one `select ()' and one `read ()'

Coming back to this bit: that select really does seem pretty doubtful.
The more I think the more it sounds like a hangover from 1.6 where
select was the only proper place to block if other threads were to
keep running.

But maybe now the right thing would be to have the scm_leave_guile or
scm_without_guile or whatever around each read/write.  Avoiding
initializing an fd_set and whatnot for select would have to be a good
thing.

If so then there'd be other places a similar leave might be wanted,
`copy-file' and `gethost' for instance (I might have mentioned them
before).  Oh well, I guess there's plenty of work for anyone who wants
to investigate ...


One thing I'm pretty sure about though is that the __MINGW32__
conditional on fport_wait_for_input can't be right.  I think we had
bug reports that mingw has pthreads now, so surely anything wanted for
pthreads will be wanted on mingw (or new enough ones or whatever).

Maybe the condition was meant to be "if threads" instead of "if not
mingw".  If configured with null-threads then there'd be no need to
worry about where any blocking happens.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-01  9:10           ` Ludovic Courtès
  2007-03-04 23:48             ` Kevin Ryde
  2007-03-07  0:20             ` Kevin Ryde
@ 2007-03-07  0:30             ` Kevin Ryde
  2007-03-07  4:45               ` Kevin Ryde
  2007-03-07  9:40               ` Ludovic Courtès
  2 siblings, 2 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-03-07  0:30 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

ludovic.courtes@laas.fr (Ludovic Courtès) writes:
>
> N calls to `scm_getc ()'

Speaking of scm_getc ... I've wondered for a while if some of the low
level reading funcs ought to be looking directly into the read buffer
instead of making a call to scm_getc for every char.  My experience
has usually been that a function call per character is very much to be
avoided if performance on sizeable input matters.

I'd have in mind simple bits like rdelim `read-line' which could do a
scan of the buffer for a newline; and possibly scm_flush_ws (within
`read') which is looking for non-whitespace and jumping over comments.
Not sure if helping the latter would be noticable speedup, but it's
nice to think that big chunks of comments in sources could be cheap to
read.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-07  0:30             ` Kevin Ryde
@ 2007-03-07  4:45               ` Kevin Ryde
  2007-03-07  9:40               ` Ludovic Courtès
  1 sibling, 0 replies; 21+ messages in thread
From: Kevin Ryde @ 2007-03-07  4:45 UTC (permalink / raw)
  To: guile-devel

I wrote:
>
> rdelim `read-line'

Oops, I confused myself.  It already looks in the buffer - it's the
model not the improvable - a few more bits could be like it ...


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-07  0:30             ` Kevin Ryde
  2007-03-07  4:45               ` Kevin Ryde
@ 2007-03-07  9:40               ` Ludovic Courtès
  1 sibling, 0 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-03-07  9:40 UTC (permalink / raw)
  To: Neil Jerram; +Cc: Guile-Devel

Hi,

Kevin Ryde <user42@zip.com.au> writes:

> Speaking of scm_getc ... I've wondered for a while if some of the low
> level reading funcs ought to be looking directly into the read buffer
> instead of making a call to scm_getc for every char.  My experience
> has usually been that a function call per character is very much to be
> avoided if performance on sizeable input matters.

You are right: `scm_getc ()' _is_ a performance bottleneck, one function
call for a single memory read (which is the common case when the port is
buffered) is way too much---`scm_getc ()' often shows up in the top 10
in Gprof's flat profile.

The whole point of having a buffer in a publicly-exposed struct is to
avoid function call overhead when accessing it.  Thus, `scm_getc ()'
should be inlined.

(You might have read that Guile-Reader is noticeably faster than
`scm_read ()'.  One of the reasons for this is that it inlines
`scm_getc ()' in the generated lightning code.)


Unfortunately, the machinery in `inline.h' makes it quite inconvenient
to add inline functions, and forces us to put all of them in a single
file.  GMP has a similar but slightly "cleaner" mechanism, which maybe
we could build upon to improve ours.

There are definitely a number of functions candidate for inlining in
Guile, typically functions that only execute a handful of instructions.

Thanks,
Ludovic.


_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* `scm_std_select ()' call in `fport_wait_for_input ()'
  2007-02-26 23:27     ` Kevin Ryde
  2007-02-28  9:47       ` Ludovic Courtès
@ 2007-05-13 17:13       ` Ludovic Courtès
  1 sibling, 0 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-05-13 17:13 UTC (permalink / raw)
  To: guile-devel

Hi,

Kevin Ryde <user42@zip.com.au> writes:

> ludovic.courtes@laas.fr (Ludovic Courtès) writes:

>> BTW, do you know what the purpose of `fport_wait_for_input ()' is?
>
> Maybe left from the 1.6 cooperative threads.
>
>> It
>> does nothing for O_NONBLOCK streams and waits for events otherwise.
>> Since, for blocking streams, `read ()' does not return until either EOF
>> is reached or at least one octet was read, `fport_wait_for_input ()'
>> seems redundant.
>
> Ahh, hang on, I wonder if it's a hack to do an "exit guile" while
> blocked within a read(), thus allowing gc to run in other threads.
>
> If that's true then presumably the write side is afflicted too, as
> well as various other potentially blocking operations like
> read-string!/partial and gethost.

In node "Blocking", the manual reads this:

  A thread must not block outside of a libguile function while it is in
  guile mode.  The following functions can be used to temporily leave
  guile mode or to perform some common blocking operations in a
  supported way.

The `scm_std_select ()' call does indeed a "leave guile mode", thereby
allowing GC to run, should input data be currently unavailable.  I guess
that's why it's here.

A alternative way to do this would be to have `fport_wait_for_input ()'
call `read(2)' through `scm_without_guile ()'.  This would make the
intent clearer, and may perform slightly better (removes one syscall
from the I/O path).

What do you think?

Thanks,
Ludovic.



_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: Unbuffered socket I/O
  2007-03-05 23:15             ` Kevin Ryde
@ 2007-08-07 16:01               ` Ludovic Courtès
  0 siblings, 0 replies; 21+ messages in thread
From: Ludovic Courtès @ 2007-08-07 16:01 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 441 bytes --]

Hi,

Here is an updated patch that adds `setvbuf-input' and `setvbuf-output'.

As suggested by Kevin, `setvbuf-{input,output}' now take a MODE
argument.  Unlike previous versions of the patch,
`setvbuf-{input,output}' simply discard the input/output buffer.  In
addition, `setvbuf' and `fport_buffer_add ()' are now implemented in
terms of the two other.

I'd be grateful if someone (Kevin?) could review this.

Thanks in advance,
Ludovic.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: The patch --]
[-- Type: text/x-patch, Size: 10912 bytes --]

--- orig/libguile/fports.c
+++ mod/libguile/fports.c
@@ -86,71 +86,78 @@
 
 scm_t_bits scm_tc16_fport;
 
+static void fport_flush (SCM port);
+
+/* Hints passed to the GC allocation functions for port buffers and file
+   ports.  */
+static const char gc_port_buffer_hint[] = "port buffer";
+static const char gc_file_port_hint[]   = "file port";
 
 /* default buffer size, used if the O/S won't supply a value.  */
 static const size_t default_buffer_size = 1024;
 
-/* create FPORT buffer with specified sizes (or -1 to use default size or
-   0 for no buffer.  */
+/* Create FPORT buffer with specified sizes (or -1 to use default size or
+   0 for no buffer).  */
 static void
-scm_fport_buffer_add (SCM port, long read_size, int write_size)
+fport_buffer_add (SCM port, long c_buffer_size)
 #define FUNC_NAME "scm_fport_buffer_add"
 {
+  SCM buffer_size, mode;
+  int c_mode = _IOFBF;
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
 
-  if (read_size == -1 || write_size == -1)
+  if (c_buffer_size == -1)
     {
-      size_t default_size;
 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
       struct stat st;
       scm_t_fport *fp = SCM_FSTREAM (port);
-      
-      default_size = (fstat (fp->fdes, &st) == -1) ? default_buffer_size
+
+      c_buffer_size = (fstat (fp->fdes, &st) == -1) ? default_buffer_size
 	: st.st_blksize;
 #else
-      default_size = default_buffer_size;
+      c_buffer_size = default_buffer_size;
 #endif
-      if (read_size == -1)
-	read_size = default_size;
-      if (write_size == -1)
-	write_size = default_size;
     }
 
-  if (SCM_INPUT_PORT_P (port) && read_size > 0)
-    {
-      pt->read_buf = scm_gc_malloc (read_size, "port buffer");
-      pt->read_pos = pt->read_end = pt->read_buf;
-      pt->read_buf_size = read_size;
-    }
+  buffer_size = scm_from_long (c_buffer_size);
+  c_mode = (SCM_CELL_WORD_0 (port) & SCM_BUFLINE) ? _IOLBF : _IOFBF;
+  mode = scm_from_int (c_mode);
+
+  if (SCM_INPUT_PORT_P (port) && (buffer_size > 0))
+    scm_setvbuf_input (port, mode, buffer_size);
   else
     {
       pt->read_pos = pt->read_buf = pt->read_end = &pt->shortbuf;
       pt->read_buf_size = 1;
     }
 
-  if (SCM_OUTPUT_PORT_P (port) && write_size > 0)
-    {
-      pt->write_buf = scm_gc_malloc (write_size, "port buffer");
-      pt->write_pos = pt->write_buf;
-      pt->write_buf_size = write_size;
-    }
+  if (SCM_OUTPUT_PORT_P (port) && (buffer_size > 0))
+    scm_setvbuf_output (port, mode, buffer_size);
   else
     {
       pt->write_buf = pt->write_pos = &pt->shortbuf;
       pt->write_buf_size = 1;
     }
-
-  pt->write_end = pt->write_buf + pt->write_buf_size;
-  if (read_size > 0 || write_size > 0)
-    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~SCM_BUF0);
-  else
-    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
 }
 #undef FUNC_NAME
 
+/* Convert MODE to a C buffering mode.  */
+static inline int
+scm_to_buffer_mode (SCM mode, const char *func_name)
+{
+  int c_mode;
+
+  c_mode = scm_to_int (mode);
+  if (c_mode != _IONBF && c_mode != _IOFBF && c_mode != _IOLBF)
+    scm_out_of_range (func_name, mode);
+
+  return c_mode;
+}
+
 SCM_DEFINE (scm_setvbuf, "setvbuf", 2, 1, 0, 
             (SCM port, SCM mode, SCM size),
-	    "Set the buffering mode for @var{port}.  @var{mode} can be:\n"
+	    "Set the input and output buffering mode for @var{port}.  "
+	    "@var{mode} can be:\n"
 	    "@table @code\n"
 	    "@item _IONBF\n"
 	    "non-buffered\n"
@@ -162,61 +169,163 @@
 	    "@end table")
 #define FUNC_NAME s_scm_setvbuf
 {
-  int cmode;
-  long csize;
-  scm_t_port *pt;
-
   port = SCM_COERCE_OUTPORT (port);
 
-  SCM_VALIDATE_OPFPORT (1,port);
-  cmode = scm_to_int (mode);
-  if (cmode != _IONBF && cmode != _IOFBF && cmode != _IOLBF)
-    scm_out_of_range (FUNC_NAME, mode);
+  SCM_VALIDATE_OPFPORT (1, port);
+
+  if (SCM_INPUT_PORT_P (port))
+    scm_setvbuf_input (port, mode, size);
+
+  if (SCM_OUTPUT_PORT_P (port))
+    scm_setvbuf_output (port, mode, size);
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_setvbuf_input, "setvbuf-input", 2, 1, 0,
+	    (SCM port, SCM mode, SCM size),
+	    "Set the input buffering mode for @var{port}, with the "
+	    "@var{mode} and @var{size} arguments akin to those of "
+	    "@code{setvbuf}.")
+#define FUNC_NAME s_scm_setvbuf_input
+{
+  scm_t_port *c_port;
+  int c_mode;
+  size_t c_size;
+  unsigned char *new_buf;
 
-  if (cmode == _IOLBF)
+  SCM_VALIDATE_FPORT (1, port);
+  SCM_VALIDATE_OPINPORT (1, port);
+
+  c_port = SCM_PTAB_ENTRY (port);
+  c_mode = scm_to_buffer_mode (mode, FUNC_NAME);
+  if (SCM_UNBNDP (size))
     {
-      SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUFLINE);
-      cmode = _IOFBF;
+      if (c_mode == _IOFBF)
+	c_size = -1;
+      else
+	c_size = 0;
+    }
+  else
+    {
+      c_size = scm_to_int (size);
+      if (c_size < 0 || (c_mode == _IONBF && c_size > 0))
+	scm_out_of_range (FUNC_NAME, size);
+    }
+
+  if (c_port->read_buf == c_port->putback_buf)
+    {
+      /* Silently discard buffered and put-back chars.  */
+      c_port->read_buf = c_port->saved_read_buf;
+      c_port->read_pos = c_port->saved_read_pos;
+      c_port->read_end = c_port->saved_read_end;
+      c_port->read_buf_size = c_port->saved_read_buf_size;
+    }
+
+  if (c_size == 0)
+    {
+      new_buf = &c_port->shortbuf, c_size = 1;
+      if (c_port->read_buf != &c_port->shortbuf)
+	scm_gc_free (c_port->read_buf, c_port->read_buf_size,
+		     gc_port_buffer_hint);
     }
   else
     {
-      SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~(scm_t_bits)SCM_BUFLINE);
+      if (c_port->read_buf != &c_port->shortbuf)
+	new_buf = scm_gc_realloc (c_port->read_buf,
+				  c_port->read_buf_size, c_size,
+				  gc_port_buffer_hint);
+      else
+	new_buf = scm_gc_malloc (c_size, gc_port_buffer_hint);
     }
 
+  c_port->read_buf = new_buf;
+  c_port->read_end = new_buf;
+  c_port->read_pos = new_buf;
+  c_port->read_buf_size = c_size;
+
+  if ((c_port->read_buf_size == 0) && (c_port->write_buf_size == 0))
+    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+  else
+    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~SCM_BUF0);
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_setvbuf_output, "setvbuf-output", 2, 1, 0,
+	    (SCM port, SCM mode, SCM size),
+	    "Set the output buffering mode for @var{port}, with the "
+	    "@var{mode} and @var{size} arguments akin to those of "
+	    "@code{setvbuf}.")
+#define FUNC_NAME s_scm_setvbuf_output
+{
+  scm_t_port *c_port;
+  int c_mode;
+  size_t c_size;
+  unsigned char *new_buf;
+
+  SCM_VALIDATE_FPORT (1, port);
+
+  c_port = SCM_PTAB_ENTRY (port);
+  c_mode = scm_to_buffer_mode (mode, FUNC_NAME);
   if (SCM_UNBNDP (size))
     {
-      if (cmode == _IOFBF)
-	csize = -1;
+      if (c_mode == _IOFBF)
+	c_size = -1;
       else
-	csize = 0;
+	c_size = 0;
     }
   else
     {
-      csize = scm_to_int (size);
-      if (csize < 0 || (cmode == _IONBF && csize > 0))
+      c_size = scm_to_int (size);
+      if (c_size < 0 || (c_mode == _IONBF && c_size > 0))
 	scm_out_of_range (FUNC_NAME, size);
     }
 
-  pt = SCM_PTAB_ENTRY (port);
+  /* Note: line buffering is only supported by `fport_write ()'.  */
+  if (c_mode == _IOLBF)
+    {
+      SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUFLINE);
+      c_mode = _IOFBF;
+    }
+  else
+    SCM_SET_CELL_WORD_0 (port,
+			 SCM_CELL_WORD_0 (port) & ~(scm_t_bits) SCM_BUFLINE);
 
-  /* silently discards buffered and put-back chars.  */
-  if (pt->read_buf == pt->putback_buf)
+  if (c_size == 0)
     {
-      pt->read_buf = pt->saved_read_buf;
-      pt->read_pos = pt->saved_read_pos;
-      pt->read_end = pt->saved_read_end;
-      pt->read_buf_size = pt->saved_read_buf_size;
+      new_buf = &c_port->shortbuf, c_size = 1;
+      if (c_port->write_buf != &c_port->shortbuf)
+	scm_gc_free (c_port->write_buf, c_port->write_buf_size,
+		     gc_port_buffer_hint);
     }
-  if (pt->read_buf != &pt->shortbuf)
-    scm_gc_free (pt->read_buf, pt->read_buf_size, "port buffer");
-  if (pt->write_buf != &pt->shortbuf)
-    scm_gc_free (pt->write_buf, pt->write_buf_size, "port buffer");
+  else
+    {
+      if (c_port->write_buf != &c_port->shortbuf)
+	new_buf = scm_gc_realloc (c_port->write_buf,
+				  c_port->write_buf_size, c_size,
+				  gc_port_buffer_hint);
+      else
+	new_buf = scm_gc_malloc (c_size, gc_port_buffer_hint);
+    }
+
+  c_port->write_buf = new_buf;
+  c_port->write_end = new_buf + c_size;
+  c_port->write_pos = new_buf;
+  c_port->write_buf_size = c_size;
+
+  if ((c_port->read_buf_size == 0) && (c_port->write_buf_size == 0))
+    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) | SCM_BUF0);
+  else
+    SCM_SET_CELL_WORD_0 (port, SCM_CELL_WORD_0 (port) & ~SCM_BUF0);
 
-  scm_fport_buffer_add (port, csize, csize);
   return SCM_UNSPECIFIED;
 }
 #undef FUNC_NAME
 
+
 /* Move ports with the specified file descriptor to new descriptors,
  * resetting the revealed count to 0.
  */
@@ -459,15 +568,16 @@
   pt = SCM_PTAB_ENTRY(port);
   {
     scm_t_fport *fp
-      = (scm_t_fport *) scm_gc_malloc (sizeof (scm_t_fport), "file port");
+      = (scm_t_fport *) scm_gc_malloc (sizeof (scm_t_fport),
+				       gc_file_port_hint);
 
     fp->fdes = fdes;
     pt->rw_random = SCM_FDES_RANDOM_P (fdes);
     SCM_SETSTREAM (port, fp);
     if (mode_bits & SCM_BUF0)
-      scm_fport_buffer_add (port, 0, 0);
+      fport_buffer_add (port, 0);
     else
-      scm_fport_buffer_add (port, -1, -1);
+      fport_buffer_add (port, -1);
   }
   SCM_SET_FILENAME (port, name);
   scm_i_pthread_mutex_unlock (&scm_i_port_table_mutex);
@@ -583,8 +693,6 @@
 }
 #endif /* !__MINGW32__ */
 
-static void fport_flush (SCM port);
-
 /* fill a port's read-buffer with a single read.  returns the first
    char or EOF if end of file.  */
 static int
@@ -892,10 +1000,10 @@
   if (pt->read_buf == pt->putback_buf)
     pt->read_buf = pt->saved_read_buf;
   if (pt->read_buf != &pt->shortbuf)
-    scm_gc_free (pt->read_buf, pt->read_buf_size, "port buffer");
+    scm_gc_free (pt->read_buf, pt->read_buf_size, gc_port_buffer_hint);
   if (pt->write_buf != &pt->shortbuf)
-    scm_gc_free (pt->write_buf, pt->write_buf_size, "port buffer");
-  scm_gc_free (fp, sizeof (*fp), "file port");
+    scm_gc_free (pt->write_buf, pt->write_buf_size, gc_port_buffer_hint);
+  scm_gc_free (fp, sizeof (*fp), gc_file_port_hint);
   return rv;
 }
 
--- orig/libguile/fports.h
+++ mod/libguile/fports.h
@@ -49,6 +49,8 @@
 \f
 SCM_API SCM scm_setbuf0 (SCM port);
 SCM_API SCM scm_setvbuf (SCM port, SCM mode, SCM size);
+SCM_API SCM scm_setvbuf_input (SCM port, SCM mode, SCM size);
+SCM_API SCM scm_setvbuf_output (SCM port, SCM mode, SCM size);
 SCM_API void scm_evict_ports (int fd);
 SCM_API SCM scm_open_file (SCM filename, SCM modes);
 SCM_API SCM scm_fdes_to_port (int fdes, char *mode, SCM name);


[-- Attachment #3: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2007-08-07 16:01 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-02-23 17:09 Unbuffered socket I/O Ludovic Courtès
2007-02-23 22:36 ` Neil Jerram
2007-02-25 22:57 ` Kevin Ryde
2007-02-26 14:07   ` Ludovic Courtès
2007-02-26 20:32     ` Neil Jerram
2007-02-26 23:35       ` Kevin Ryde
2007-02-27  9:11       ` Ludovic Courtès
2007-02-28 21:24         ` Kevin Ryde
2007-03-01  9:10           ` Ludovic Courtès
2007-03-04 23:48             ` Kevin Ryde
2007-03-07  0:20             ` Kevin Ryde
2007-03-07  0:30             ` Kevin Ryde
2007-03-07  4:45               ` Kevin Ryde
2007-03-07  9:40               ` Ludovic Courtès
2007-02-26 23:27     ` Kevin Ryde
2007-02-28  9:47       ` Ludovic Courtès
2007-02-28 20:59         ` Kevin Ryde
2007-03-01 10:44           ` Ludovic Courtès
2007-03-05 23:15             ` Kevin Ryde
2007-08-07 16:01               ` Ludovic Courtès
2007-05-13 17:13       ` `scm_std_select ()' call in `fport_wait_for_input ()' Ludovic Courtès

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).