* Threads performance issue.
@ 2009-02-16 15:15 Rémi Dewitte
2009-02-16 15:28 ` [Caml-list] " Michał Maciejewski
` (2 more replies)
0 siblings, 3 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-16 15:15 UTC (permalink / raw)
To: caml-list
[-- Attachment #1: Type: text/plain, Size: 679 bytes --]
Hello,
I would like to read two files in two different threads.
I have made a first version reading the first then the second and it takes
2.8s (native).
I decided to make a threaded version and before any use of thread I realized
that just linking no even using it to the threads library makes my first
version of the program to run in 12s !
I use pcre, extlib, csv libraries as well.
I guess it might come from GC slowing down thinks here, doesn't it ? Where
can it come from otherwise ? Is there a workaround or something I should
know ?
Can ocaml use multiple cores ?
Do you have few pointers on libraries to make parallel I/Os ?
Thanks,
Rémi
[-- Attachment #2: Type: text/html, Size: 721 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Caml-list] Threads performance issue.
2009-02-16 15:15 Threads performance issue Rémi Dewitte
@ 2009-02-16 15:28 ` Michał Maciejewski
2009-02-16 15:32 ` Rémi Dewitte
2009-02-16 16:32 ` Sylvain Le Gall
2009-02-16 16:47 ` [Caml-list] " Yaron Minsky
2 siblings, 1 reply; 21+ messages in thread
From: Michał Maciejewski @ 2009-02-16 15:28 UTC (permalink / raw)
To: caml-list
Hi,
2009/2/16 Rémi Dewitte <remi@gide.net>:
> I guess it might come from GC slowing down thinks here, doesn't it ?
I don't think so. Why do you think it's GC?
> Can ocaml use multiple cores ?
No and as far as I know it's because of GC. ;-)
regards
Miichal
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-16 15:28 ` [Caml-list] " Michał Maciejewski
@ 2009-02-16 15:32 ` Rémi Dewitte
2009-02-16 15:42 ` David Allsopp
0 siblings, 1 reply; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-16 15:32 UTC (permalink / raw)
To: Michał Maciejewski; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 812 bytes --]
On Mon, Feb 16, 2009 at 16:28, Michał Maciejewski <michal.m.pl@gmail.com>wrote:
> Hi,
>
> 2009/2/16 Rémi Dewitte <remi@gide.net>:
> > I guess it might come from GC slowing down thinks here, doesn't it ?
> I don't think so. Why do you think it's GC?
>
Bad guess :) !
Any hint why just linking makes things slow ?
>
> > Can ocaml use multiple cores ?
> No and as far as I know it's because of GC. ;-)
>
Ok that's a shame but I will live with :)
>
> regards
> Miichal
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
[-- Attachment #2: Type: text/html, Size: 1868 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [Caml-list] Threads performance issue.
2009-02-16 15:32 ` Rémi Dewitte
@ 2009-02-16 15:42 ` David Allsopp
2009-02-16 16:07 ` Rémi Dewitte
0 siblings, 1 reply; 21+ messages in thread
From: David Allsopp @ 2009-02-16 15:42 UTC (permalink / raw)
To: 'Rémi Dewitte', 'Michał Maciejewski'; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 1101 bytes --]
Which OS (and port, if applicable) are you using?
From: caml-list-bounces@yquem.inria.fr [mailto:caml-list-bounces@yquem.inria.fr] On Behalf Of Rémi Dewitte
Sent: 16 February 2009 15:33
To: Michał Maciejewski
Cc: caml-list@inria.fr
Subject: Re: [Caml-list] Threads performance issue.
On Mon, Feb 16, 2009 at 16:28, Michał Maciejewski <michal.m.pl@gmail.com> wrote:
Hi,
2009/2/16 Rémi Dewitte <remi@gide.net>:
> I guess it might come from GC slowing down thinks here, doesn't it ?
I don't think so. Why do you think it's GC?
Bad guess :) !
Any hint why just linking makes things slow ?
> Can ocaml use multiple cores ?
No and as far as I know it's because of GC. ;-)
Ok that's a shame but I will live with :)
regards
Miichal
_______________________________________________
Caml-list mailing list. Subscription management:
http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
Archives: http://caml.inria.fr
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
Bug reports: http://caml.inria.fr/bin/caml-bugs
[-- Attachment #2: Type: text/html, Size: 7125 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-16 15:42 ` David Allsopp
@ 2009-02-16 16:07 ` Rémi Dewitte
0 siblings, 0 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-16 16:07 UTC (permalink / raw)
To: David Allsopp; +Cc: Michał Maciejewski, caml-list
[-- Attachment #1: Type: text/plain, Size: 1373 bytes --]
Ubuntu 8.10, kernel 2.6.27-11-generic
Ocaml 3.10.2 shipped with the distrib
Thanks a lot,
Rémi
On Mon, Feb 16, 2009 at 16:42, David Allsopp <dra-news@metastack.com> wrote:
> Which OS (and port, if applicable) are you using?
>
>
>
> *From:* caml-list-bounces@yquem.inria.fr [mailto:
> caml-list-bounces@yquem.inria.fr] *On Behalf Of *Rémi Dewitte
> *Sent:* 16 February 2009 15:33
> *To:* Michał Maciejewski
> *Cc:* caml-list@inria.fr
> *Subject:* Re: [Caml-list] Threads performance issue.
>
>
>
>
>
> On Mon, Feb 16, 2009 at 16:28, Michał Maciejewski <michal.m.pl@gmail.com>
> wrote:
>
> Hi,
>
> 2009/2/16 Rémi Dewitte <remi@gide.net>:
>
> > I guess it might come from GC slowing down thinks here, doesn't it ?
>
> I don't think so. Why do you think it's GC?
>
> Bad guess :) !
> Any hint why just linking makes things slow ?
>
>
>
> > Can ocaml use multiple cores ?
>
> No and as far as I know it's because of GC. ;-)
>
> Ok that's a shame but I will live with :)
>
>
> regards
> Miichal
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
>
>
[-- Attachment #2: Type: text/html, Size: 3855 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Threads performance issue.
2009-02-16 15:15 Threads performance issue Rémi Dewitte
2009-02-16 15:28 ` [Caml-list] " Michał Maciejewski
@ 2009-02-16 16:32 ` Sylvain Le Gall
2009-02-17 13:52 ` [Caml-list] " Frédéric Gava
2009-02-16 16:47 ` [Caml-list] " Yaron Minsky
2 siblings, 1 reply; 21+ messages in thread
From: Sylvain Le Gall @ 2009-02-16 16:32 UTC (permalink / raw)
To: caml-list
Hello,
On 16-02-2009, Rémi Dewitte <remi@gide.net> wrote:
> --===============0282778124==
> Content-Type: multipart/alternative; boundary=00504502b0791d7c5b04630aa761
>
> --00504502b0791d7c5b04630aa761
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: quoted-printable
>
> Hello,
>
> I would like to read two files in two different threads.
>
> I have made a first version reading the first then the second and it takes
> 2.8s (native).
>
> I decided to make a threaded version and before any use of thread I realize=
> d
> that just linking no even using it to the threads library makes my first
> version of the program to run in 12s !
>
There is a small function call to handle thread
(caml_(enter|leave)_blocking_section). I don't know how much it cost in
term of performance but I am under the impression that it cost more time
than you win. These function calls can be found in many files all around
the OCaml source distribution...
> I use pcre, extlib, csv libraries as well.
>
Some of this library can have a high cost for thread synchronisation on global
variable. You need to investigate.
> I guess it might come from GC slowing down thinks here, doesn't it ? Where
> can it come from otherwise ? Is there a workaround or something I should
> know ?
Maybe... You need to look at external library and to benchmark your own
code... This is not an easy task.
>
> Can ocaml use multiple cores ?
>
As advertised in the OCaml documentation:
http://caml.inria.fr/pub/docs/manual-ocaml/manual038.html
The threads library is implemented by time-sharing on a single
processor. It will not take advantage of multi-processor machines. Using
this library will therefore never make programs run faster. However,
many programs are easier to write when structured as several
communicating processes.
One of the point is that the GC doesn't take advantage of multiple-core.
Current GC that support this feature are slower than OCaml
single-threaded GC...
> Do you have few pointers on libraries to make parallel I/Os ?
>
Since you are running a fairly recent Linux kernel, I recommend you:
https://forge.ocamlcore.org/projects/libaio-ocaml/
which should allow you to use AIO (asynchronous IO in the kernel, see
"man aio_read").
Now on a more "ask-yourself" tone:
I have tried using thread to speed up IO on multiple core (in C code).
It is really tricky to get something that really work faster. In fact,
for reading you don't get performance at all when using threaded IO. I
am still asking myself why. I think it as something todo with the fact
that when you generate too much read request, the OS begin to do
inefficient I/O seek all around (almost no effect on Linux, timex4 on
Windows). As a matter of fact (for now), using non-threaded Unix.read
with 16k buffer and threaded Unix.write with 4M buffer is the most
efficient I/O scheme.
All in all, I think you should not try to use thread to improve your
software performance in OCaml - or rely on low-level asynchronous IO
(aio).
Regards,
Sylvain Le Gall
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-16 15:15 Threads performance issue Rémi Dewitte
2009-02-16 15:28 ` [Caml-list] " Michał Maciejewski
2009-02-16 16:32 ` Sylvain Le Gall
@ 2009-02-16 16:47 ` Yaron Minsky
2009-02-16 17:37 ` Rémi Dewitte
2 siblings, 1 reply; 21+ messages in thread
From: Yaron Minsky @ 2009-02-16 16:47 UTC (permalink / raw)
To: Rémi Dewitte; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 1293 bytes --]
2009/2/16 Rémi Dewitte <remi@gide.net>
> Hello,
>
> I would like to read two files in two different threads.
>
> I have made a first version reading the first then the second and it takes
> 2.8s (native).
>
> I decided to make a threaded version and before any use of thread I
> realized that just linking no even using it to the threads library makes my
> first version of the program to run in 12s !
Do you have a short benchmark you can post? The idea that the
thread-overhead would make a difference like that, particularly for IO-bound
code (which I'm guessing this is) is pretty surprising.
y
>
> I use pcre, extlib, csv libraries as well.
>
> I guess it might come from GC slowing down thinks here, doesn't it ? Where
> can it come from otherwise ? Is there a workaround or something I should
> know ?
>
> Can ocaml use multiple cores ?
>
> Do you have few pointers on libraries to make parallel I/Os ?
>
> Thanks,
> Rémi
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
>
[-- Attachment #2: Type: text/html, Size: 2033 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-16 16:47 ` [Caml-list] " Yaron Minsky
@ 2009-02-16 17:37 ` Rémi Dewitte
2009-02-17 7:40 ` Rémi Dewitte
0 siblings, 1 reply; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-16 17:37 UTC (permalink / raw)
To: yminsky; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 5317 bytes --]
Yaron,
I use a slightly modified version of the CSV library's load_rows . Here is
the main code which is highly imperative style. I might transform it in
purely functional style ?
The main program is :
open Printf;;
open Sys;;
let timed_exec start_message f =
print_string start_message;
let st1 = time () in
let r = f () in
print_endline ("done in " ^ (string_of_float ((time ()) -. st1)) );
r;;
(* This line enabled makes the program really slow ! *)
let run_threaded f = Thread.create (fun () -> f (); Thread.exit ()) ()
let () = timed_exec "Reading data " (fun () ->
load_rows (fun _ -> ()) (open_in "file1.csv");
load_rows (fun _ -> ()) (open_in "file2.csv");
()
)
The load_rows :
let load_rows ?(separator = ',') ?(nread = -1) f chan =
let nr = ref 0 in
let row = ref [] in (* Current row. *)
let field = ref [] in (* Current field. *)
let state = ref StartField in (* Current state. *)
let end_of_field () =
let field_list = List.rev !field in
let field_len = List.length field_list in
let field_str = String.create field_len in
let rec loop i = function
[] -> ()
| x :: xs ->
field_str.[i] <- x;
loop (i+1) xs
in
loop 0 field_list;
row := (Some field_str) :: !row;
field := [];
state := StartField
in
let empty_field () =
row := None :: !row;
field := [];
state := StartField
in
let end_of_row () =
let row_list = List.rev !row in
f row_list;
row := [];
state := StartField;
nr := !nr + 1;
in
let rec loop () =
let c = input_char chan in
if c != '\r' then ( (* Always ignore \r characters. *)
match !state with
StartField -> (* Expecting quote or other char. *)
if c = '"' then (
state := InQuotedField;
field := []
) else if c = separator then (* Empty field. *)
empty_field ()
else if c = '\n' then ( (* Empty field, end of row. *)
empty_field ();
end_of_row ()
) else (
state := InUnquotedField;
field := [c]
)
| InUnquotedField -> (* Reading chars to end of field. *)
if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else
field := c :: !field
| InQuotedField -> (* Reading chars to end of field. *)
if c = '"' then
state := InQuotedFieldAfterQuote
else
field := c :: !field
| InQuotedFieldAfterQuote ->
if c = '"' then ( (* Doubled quote. *)
field := c :: !field;
state := InQuotedField
) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
field := '\000' :: !field;
state := InQuotedField
) else if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else ( (* Bad single quote in field. *)
field := c :: '"' :: !field;
state := InQuotedField
)
); (* end of match *)
if( nread < 0 or !nr < nread) then loop () else ()
in
try
loop ()
with
End_of_file ->
(* Any part left to write out? *)
(match !state with
StartField ->
if !row <> [] then
( empty_field (); end_of_row () )
| InUnquotedField | InQuotedFieldAfterQuote ->
end_of_field (); end_of_row ()
| InQuotedField ->
raise (Bad_CSV_file "Missing end quote after quoted field.")
)
Thanks,
Rémi
On Mon, Feb 16, 2009 at 17:47, Yaron Minsky <yminsky@gmail.com> wrote:
> 2009/2/16 Rémi Dewitte <remi@gide.net>
>
>> Hello,
>>
>> I would like to read two files in two different threads.
>>
>> I have made a first version reading the first then the second and it takes
>> 2.8s (native).
>>
>> I decided to make a threaded version and before any use of thread I
>> realized that just linking no even using it to the threads library makes my
>> first version of the program to run in 12s !
>
>
> Do you have a short benchmark you can post? The idea that the
> thread-overhead would make a difference like that, particularly for IO-bound
> code (which I'm guessing this is) is pretty surprising.
>
> y
>
>
>>
>> I use pcre, extlib, csv libraries as well.
>>
>> I guess it might come from GC slowing down thinks here, doesn't it ? Where
>> can it come from otherwise ? Is there a workaround or something I should
>> know ?
>>
>> Can ocaml use multiple cores ?
>>
>> Do you have few pointers on libraries to make parallel I/Os ?
>>
>> Thanks,
>> Rémi
>>
>> _______________________________________________
>> Caml-list mailing list. Subscription management:
>> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
>> Archives: http://caml.inria.fr
>> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
>> Bug reports: http://caml.inria.fr/bin/caml-bugs
>>
>>
>
[-- Attachment #2: Type: text/html, Size: 9597 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-16 17:37 ` Rémi Dewitte
@ 2009-02-17 7:40 ` Rémi Dewitte
2009-02-17 8:59 ` Mark Shinwell
2009-02-17 10:07 ` Sylvain Le Gall
0 siblings, 2 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-17 7:40 UTC (permalink / raw)
To: yminsky; +Cc: caml-list
[-- Attachment #1.1: Type: text/plain, Size: 6548 bytes --]
I have made some further experiments.
I have a functional version of the reading algorithm. I have the original
imperative version of the algorithm.
Either it is linked to thread (T) or not (X). Either it uses extlib (E) or
not (X).
Results are.
XX TX XE TE
Imperative | 3.37 | 7.80 | 3.56 | 8.40
Functional | 4.20 | 8.28 | 4.47 | 9.08
test.csv is a 21mo file with ~13k rows and a thousands of columns on a 15rpm
disk.
ocaml version : 3.11.0
uname -a gives
Linux localhost 2.6.28.4-server-1mnb #1 SMP Mon Feb 9 09:05:19 EST 2009 i686
Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz GNU/Linux
While I think I have to find improvements to the functional version, I
struggle finding a rationale behind this high loss of performance while I am
not even using threads, just linking to...
Cheers,
Rémi
On Mon, Feb 16, 2009 at 18:37, Rémi Dewitte <remi@gide.net> wrote:
> Yaron,
>
> I use a slightly modified version of the CSV library's load_rows . Here is
> the main code which is highly imperative style. I might transform it in
> purely functional style ?
>
> The main program is :
>
> open Printf;;
> open Sys;;
> let timed_exec start_message f =
> print_string start_message;
> let st1 = time () in
> let r = f () in
> print_endline ("done in " ^ (string_of_float ((time ()) -. st1)) );
> r;;
>
> (* This line enabled makes the program really slow ! *)
> let run_threaded f = Thread.create (fun () -> f (); Thread.exit ()) ()
>
> let () = timed_exec "Reading data " (fun () ->
> load_rows (fun _ -> ()) (open_in "file1.csv");
> load_rows (fun _ -> ()) (open_in "file2.csv");
> ()
> )
>
> The load_rows :
> let load_rows ?(separator = ',') ?(nread = -1) f chan =
> let nr = ref 0 in
> let row = ref [] in (* Current row. *)
> let field = ref [] in (* Current field. *)
> let state = ref StartField in (* Current state. *)
> let end_of_field () =
> let field_list = List.rev !field in
> let field_len = List.length field_list in
> let field_str = String.create field_len in
> let rec loop i = function
> [] -> ()
> | x :: xs ->
> field_str.[i] <- x;
> loop (i+1) xs
> in
> loop 0 field_list;
> row := (Some field_str) :: !row;
> field := [];
> state := StartField
> in
> let empty_field () =
> row := None :: !row;
> field := [];
> state := StartField
> in
> let end_of_row () =
> let row_list = List.rev !row in
> f row_list;
> row := [];
> state := StartField;
> nr := !nr + 1;
> in
> let rec loop () =
> let c = input_char chan in
> if c != '\r' then ( (* Always ignore \r characters. *)
> match !state with
> StartField -> (* Expecting quote or other char. *)
> if c = '"' then (
> state := InQuotedField;
> field := []
> ) else if c = separator then (* Empty field. *)
> empty_field ()
> else if c = '\n' then ( (* Empty field, end of row. *)
> empty_field ();
> end_of_row ()
> ) else (
> state := InUnquotedField;
> field := [c]
> )
> | InUnquotedField -> (* Reading chars to end of field. *)
> if c = separator then (* End of field. *)
> end_of_field ()
> else if c = '\n' then ( (* End of field and end of row. *)
> end_of_field ();
> end_of_row ()
> ) else
> field := c :: !field
> | InQuotedField -> (* Reading chars to end of field. *)
> if c = '"' then
> state := InQuotedFieldAfterQuote
> else
> field := c :: !field
> | InQuotedFieldAfterQuote ->
> if c = '"' then ( (* Doubled quote. *)
> field := c :: !field;
> state := InQuotedField
> ) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
> field := '\000' :: !field;
> state := InQuotedField
> ) else if c = separator then (* End of field. *)
> end_of_field ()
> else if c = '\n' then ( (* End of field and end of row. *)
> end_of_field ();
> end_of_row ()
> ) else ( (* Bad single quote in field. *)
> field := c :: '"' :: !field;
> state := InQuotedField
> )
> ); (* end of match *)
> if( nread < 0 or !nr < nread) then loop () else ()
> in
> try
> loop ()
> with
> End_of_file ->
> (* Any part left to write out? *)
> (match !state with
> StartField ->
> if !row <> [] then
> ( empty_field (); end_of_row () )
> | InUnquotedField | InQuotedFieldAfterQuote ->
> end_of_field (); end_of_row ()
> | InQuotedField ->
> raise (Bad_CSV_file "Missing end quote after quoted field.")
> )
>
>
> Thanks,
> Rémi
>
>
> On Mon, Feb 16, 2009 at 17:47, Yaron Minsky <yminsky@gmail.com> wrote:
>
>> 2009/2/16 Rémi Dewitte <remi@gide.net>
>>
>>> Hello,
>>>
>>> I would like to read two files in two different threads.
>>>
>>> I have made a first version reading the first then the second and it
>>> takes 2.8s (native).
>>>
>>> I decided to make a threaded version and before any use of thread I
>>> realized that just linking no even using it to the threads library makes my
>>> first version of the program to run in 12s !
>>
>>
>> Do you have a short benchmark you can post? The idea that the
>> thread-overhead would make a difference like that, particularly for IO-bound
>> code (which I'm guessing this is) is pretty surprising.
>>
>> y
>>
>>
>>>
>>> I use pcre, extlib, csv libraries as well.
>>>
>>> I guess it might come from GC slowing down thinks here, doesn't it ?
>>> Where can it come from otherwise ? Is there a workaround or something I
>>> should know ?
>>>
>>> Can ocaml use multiple cores ?
>>>
>>> Do you have few pointers on libraries to make parallel I/Os ?
>>>
>>> Thanks,
>>> Rémi
>>>
>>> _______________________________________________
>>> Caml-list mailing list. Subscription management:
>>> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
>>> Archives: http://caml.inria.fr
>>> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
>>> Bug reports: http://caml.inria.fr/bin/caml-bugs
>>>
>>>
>>
>
[-- Attachment #1.2: Type: text/html, Size: 11086 bytes --]
[-- Attachment #2: transf.ml --]
[-- Type: application/octet-stream, Size: 3457 bytes --]
(* open ExtLib *)
(** Slithly modified copy from module CSV *)
exception Bad_CSV_file of string
type state_t = StartField
| InUnquotedField
| InQuotedField
| InQuotedFieldAfterQuote
let load_rows ?(separator = ',') ?(nread = -1) f chan =
let entry = (StartField,[],[],0) in
(* let st = ref entry in *)
let string_of_field field =
let field_list = List.rev field in
let field_len = List.length field_list in
let field_str = String.create field_len in
let rec loop i = function
[] -> ()
| x :: xs ->
field_str.[i] <- x;
loop (i+1) xs
in
loop 0 field_list;
field_str
in
let end_of_field field row nr =
let sf = (string_of_field field) in
(StartField,[],(Some sf :: row),nr)
in
let empty_field row nr =
(StartField,[],None :: row,nr)
in
let end_of_row row nr =
f (List.rev row);
(StartField,[],[],nr + 1)
in
let empty_field_and_end_row row nr =
end_of_row (None :: row) nr
in
let end_of_field_and_row field row nr =
let sf = (string_of_field field) in
end_of_row (Some sf :: row) nr
in
let rec read_char chan = try
let c = input_char chan in (if c != '\r' then Some c else read_char chan)
with End_of_file -> None
in
let rec loop st =
let (state,field,row,nr) = st in
if(nr = nread) then
()
else (
let co = read_char chan in
match co with
| None ->
(match state with
| StartField -> if (row <> []) then (let _ = empty_field_and_end_row row nr in ()) else ()
| InUnquotedField | InQuotedFieldAfterQuote ->
let _ = empty_field_and_end_row row nr in ()
| InQuotedField ->
raise (Bad_CSV_file "Missing end quote after quoted field.")
)
| Some c ->
(let stn = (match state with
StartField -> (* Expecting quote or other char. *)
if c = '"' then (
(InQuotedField,[],row,nr)
) else if c = separator then (* Empty field. *)
empty_field row nr
else if c = '\n' then ( (* Empty field, end of row. *)
empty_field_and_end_row row nr
) else (
(InUnquotedField,[c],row,nr)
)
| InUnquotedField -> (* Reading chars to end of field. *)
if c = separator then (* End of field. *)
end_of_field field row nr
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field_and_row field row nr
) else
(state,c :: field,row,nr)
| InQuotedField -> (* Reading chars to end of field. *)
if c = '"' then
(InQuotedFieldAfterQuote,field,row,nr)
else
(state,c::field,row,nr)
| InQuotedFieldAfterQuote ->
if c = '"' then ( (* Doubled quote. *)
(InQuotedField,c::field,row,nr)
) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
(InQuotedField,'\000' :: field,row,nr)
) else if c = separator then (* End of field. *)
end_of_field field row nr
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field_and_row field row nr
) else ( (* Bad single quote in field. *)
(InQuotedField,c :: '"' :: field,row,nr)
)
) in loop stn )
)
in
loop entry
(* let run_threaded f = Thread.create (fun () -> f (); Thread.exit ()) () *)
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
[-- Attachment #3: transi.ml --]
[-- Type: application/octet-stream, Size: 3311 bytes --]
(* open ExtLib *)
(** Slithly modified copy from module CSV *)
exception Bad_CSV_file of string
type state_t = StartField
| InUnquotedField
| InQuotedField
| InQuotedFieldAfterQuote
let load_rows ?(separator = ',') ?(nread = -1) f chan =
let nr = ref 0 in
let row = ref [] in (* Current row. *)
let field = ref [] in (* Current field. *)
let state = ref StartField in (* Current state. *)
let end_of_field () =
let field_list = List.rev !field in
let field_len = List.length field_list in
let field_str = String.create field_len in
let rec loop i = function
[] -> ()
| x :: xs ->
field_str.[i] <- x;
loop (i+1) xs
in
loop 0 field_list;
row := (Some field_str) :: !row;
field := [];
state := StartField
in
let empty_field () =
row := None :: !row;
field := [];
state := StartField
in
let end_of_row () =
let row_list = List.rev !row in
f row_list;
row := [];
state := StartField;
nr := !nr + 1;
in
let rec loop () =
let c = input_char chan in
if c != '\r' then ( (* Always ignore \r characters. *)
match !state with
StartField -> (* Expecting quote or other char. *)
if c = '"' then (
state := InQuotedField;
field := []
) else if c = separator then (* Empty field. *)
empty_field ()
else if c = '\n' then ( (* Empty field, end of row. *)
empty_field ();
end_of_row ()
) else (
state := InUnquotedField;
field := [c]
)
| InUnquotedField -> (* Reading chars to end of field. *)
if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else
field := c :: !field
| InQuotedField -> (* Reading chars to end of field. *)
if c = '"' then
state := InQuotedFieldAfterQuote
else
field := c :: !field
| InQuotedFieldAfterQuote ->
if c = '"' then ( (* Doubled quote. *)
field := c :: !field;
state := InQuotedField
) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
field := '\000' :: !field;
state := InQuotedField
) else if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else ( (* Bad single quote in field. *)
field := c :: '"' :: !field;
state := InQuotedField
)
); (* end of match *)
if( nread < 0 or !nr < nread) then loop () else ()
in
try
loop ()
with
End_of_file ->
(* Any part left to write out? *)
(match !state with
StartField ->
if !row <> [] then
( empty_field (); end_of_row () )
| InUnquotedField | InQuotedFieldAfterQuote ->
end_of_field (); end_of_row ()
| InQuotedField ->
raise (Bad_CSV_file "Missing end quote after quoted field.")
)
(* let run_threaded f = Thread.create (fun () -> f (); Thread.exit ()) () *)
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
let () = let i = open_in "test.csv" in load_rows (fun _ -> ()) i; close_in i
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-17 7:40 ` Rémi Dewitte
@ 2009-02-17 8:59 ` Mark Shinwell
2009-02-17 9:09 ` Rémi Dewitte
2009-02-17 9:53 ` Jon Harrop
2009-02-17 10:07 ` Sylvain Le Gall
1 sibling, 2 replies; 21+ messages in thread
From: Mark Shinwell @ 2009-02-17 8:59 UTC (permalink / raw)
To: Rémi Dewitte; +Cc: caml-list
On Tue, Feb 17, 2009 at 08:40:11AM +0100, Rémi Dewitte wrote:
> I have made some further experiments.
> I have a functional version of the reading algorithm. I have the original
> imperative version of the algorithm.
> Either it is linked to thread (T) or not (X). Either it uses extlib (E) or
> not (X).
Using:
ocamlopt -o foo transf.ml
ocamlopt -thread -o foothread transf.ml unix.cmxa threads.cmxa
on local disk with a 24Mb, approx. 500,000-line CSV file, I only see a
minor slowdown with foothread as opposed to foo. (A minor slowdown would
inded be expected.) So I'm confused as to why your results are so
different.
You could use ocamlopt -p and run gprof on the resulting gmon.out file.
Mark
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-17 8:59 ` Mark Shinwell
@ 2009-02-17 9:09 ` Rémi Dewitte
2009-02-17 9:53 ` Jon Harrop
1 sibling, 0 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-17 9:09 UTC (permalink / raw)
To: Mark Shinwell; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 1025 bytes --]
You need to uncomment the line 107 with Thread calls so that it is
effectively linked to threads I think and see the difference !
I will try the profiling !
Rémi
On Tue, Feb 17, 2009 at 09:59, Mark Shinwell <mshinwell@janestcapital.com>wrote:
> On Tue, Feb 17, 2009 at 08:40:11AM +0100, Rémi Dewitte wrote:
> > I have made some further experiments.
> > I have a functional version of the reading algorithm. I have the original
> > imperative version of the algorithm.
> > Either it is linked to thread (T) or not (X). Either it uses extlib (E)
> or
> > not (X).
>
> Using:
>
> ocamlopt -o foo transf.ml
> ocamlopt -thread -o foothread transf.ml unix.cmxa threads.cmxa
>
> on local disk with a 24Mb, approx. 500,000-line CSV file, I only see a
> minor slowdown with foothread as opposed to foo. (A minor slowdown would
> inded be expected.) So I'm confused as to why your results are so
> different.
>
> You could use ocamlopt -p and run gprof on the resulting gmon.out file.
>
> Mark
>
[-- Attachment #2: Type: text/html, Size: 1513 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Threads performance issue.
2009-02-17 8:59 ` Mark Shinwell
2009-02-17 9:09 ` Rémi Dewitte
@ 2009-02-17 9:53 ` Jon Harrop
1 sibling, 0 replies; 21+ messages in thread
From: Jon Harrop @ 2009-02-17 9:53 UTC (permalink / raw)
To: caml-list
On Tuesday 17 February 2009 08:59:44 Mark Shinwell wrote:
> On Tue, Feb 17, 2009 at 08:40:11AM +0100, Rémi Dewitte wrote:
> > I have made some further experiments.
> > I have a functional version of the reading algorithm. I have the original
> > imperative version of the algorithm.
> > Either it is linked to thread (T) or not (X). Either it uses extlib (E)
> > or not (X).
>
> Using:
>
> ocamlopt -o foo transf.ml
> ocamlopt -thread -o foothread transf.ml unix.cmxa threads.cmxa
>
> on local disk with a 24Mb, approx. 500,000-line CSV file, I only see a
> minor slowdown with foothread as opposed to foo. (A minor slowdown would
> inded be expected.) So I'm confused as to why your results are so
> different.
If you uncomment Rémi's line of code that requires threading (or factor it out
into a different file and compile that as well when using threading) then you
should be able to reproduce his results as I have.
> You could use ocamlopt -p and run gprof on the resulting gmon.out file.
Indeed the profiling results are most enlightening. Without threads everything
looks great:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
38.89 0.07 0.07 3 23.33 60.00 camlTransf__loop_109
22.22 0.11 0.04 2873940 0.00 0.00 caml_ml_input_char
11.11 0.13 0.02 2873940 0.00 0.00
camlTransf__read_char_106
11.11 0.15 0.02 381696 0.00 0.00 camlTransf__code_begin
...
With threads, everything looks awful:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
22.22 0.08 0.08
caml_thread_leave_blocking_section
16.67 0.14 0.06
caml_thread_enter_blocking_section
13.89 0.19 0.05 3 16.67 50.00 camlTransf__loop_109
9.72 0.23 0.04 caml_io_mutex_unlock
5.56 0.24 0.02 3255698 0.00 0.00 caml_c_call
...
So most of the time is spent locking and unlocking OCaml's good old giant
global lock. Unfortunately the blocking calls are "spontaneous" so you cannot
track them down using a profile but, given the huge rise in time spent in
read_char, my guess is that OCaml has introduced an OS kernel lock around
every single byte read! If so, I'm surprised it is running *this* fast...
Rémi: assuming I am correct, I'd recommend using memory mapping (and rewriting
that code ;-).
--
Dr Jon Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/?e
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Threads performance issue.
2009-02-17 7:40 ` Rémi Dewitte
2009-02-17 8:59 ` Mark Shinwell
@ 2009-02-17 10:07 ` Sylvain Le Gall
2009-02-17 10:26 ` [Caml-list] " Mark Shinwell
2009-02-17 12:20 ` Yaron Minsky
1 sibling, 2 replies; 21+ messages in thread
From: Sylvain Le Gall @ 2009-02-17 10:07 UTC (permalink / raw)
To: caml-list
On 17-02-2009, Rémi Dewitte <remi@gide.net> wrote:
>
> test.csv is a 21mo file with ~13k rows and a thousands of columns on a 15rp=
> m
> disk.
>
> ocaml version : 3.11.0
>
You are using input_char and standard IO channel. This is a good choice
for non-threaded program. But in your case, I will use Unix.read with a
big buffer (32KB to 4MB) and change your program to use it. As
benchmarked by John Harrop, you are spending most of your time in
caml_enter|leave_blocking section. I think it comes from reading using
std IO channel which use 4k buffer. Using a bigger buffer will allow
less call to this two functions (but you won't win time at the end, I
think you will just reduce the difference between non-threaded and
threaded code).
Regards
Sylvain Le Gall
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 10:07 ` Sylvain Le Gall
@ 2009-02-17 10:26 ` Mark Shinwell
2009-02-17 10:50 ` Rémi Dewitte
2009-02-17 12:20 ` Yaron Minsky
1 sibling, 1 reply; 21+ messages in thread
From: Mark Shinwell @ 2009-02-17 10:26 UTC (permalink / raw)
To: remi; +Cc: caml-list
On Tue, Feb 17, 2009 at 10:07:05AM +0000, Sylvain Le Gall wrote:
> On 17-02-2009, Rémi Dewitte <remi@gide.net> wrote:
> You are using input_char and standard IO channel. This is a good choice
> for non-threaded program. But in your case, I will use Unix.read with a
> big buffer (32KB to 4MB) and change your program to use it. As
> benchmarked by John Harrop, you are spending most of your time in
> caml_enter|leave_blocking section.
This isn't quite right actually -- the profile is deceiving. It is true
that there are a lot of calls to enter/leave_blocking_section, but you're
actually being killed by the overhead of an independent locking strategy
in the channel-based I/O calls. I've measured this using some hackery
with a hex editor. When you call input_char, you acquire and then release
another lock which is specific to these calls (the global runtime lock is
often not released here). This process isn't especially cheap, so it would
be better to use one of the other channel calls to read data in larger blocks.
Mark
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 10:26 ` [Caml-list] " Mark Shinwell
@ 2009-02-17 10:50 ` Rémi Dewitte
2009-02-17 10:56 ` Mark Shinwell
2009-02-17 11:33 ` Jon Harrop
0 siblings, 2 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-17 10:50 UTC (permalink / raw)
To: Mark Shinwell; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 1621 bytes --]
Hello !
Many thanks all for your answers !
Managing to have the almost same performance whether in mutithreaded
environment or not (even not using threads for this particular task) is
something I would like to have anyway.
I'll give a try to big buffers using Using.read. Any example code around ?
And then why not try iao !
Memory mapping of the file could be done using BigArray or do I have to
write C code ?
Rémi
On Tue, Feb 17, 2009 at 11:26, Mark Shinwell <mshinwell@janestcapital.com>wrote:
> On Tue, Feb 17, 2009 at 10:07:05AM +0000, Sylvain Le Gall wrote:
> > On 17-02-2009, Rémi Dewitte <remi@gide.net> wrote:
> > You are using input_char and standard IO channel. This is a good choice
> > for non-threaded program. But in your case, I will use Unix.read with a
> > big buffer (32KB to 4MB) and change your program to use it. As
> > benchmarked by John Harrop, you are spending most of your time in
> > caml_enter|leave_blocking section.
>
> This isn't quite right actually -- the profile is deceiving. It is true
> that there are a lot of calls to enter/leave_blocking_section, but you're
> actually being killed by the overhead of an independent locking strategy
> in the channel-based I/O calls. I've measured this using some hackery
> with a hex editor. When you call input_char, you acquire and then release
> another lock which is specific to these calls (the global runtime lock is
> often not released here). This process isn't especially cheap, so it would
> be better to use one of the other channel calls to read data in larger
> blocks.
>
> Mark
>
[-- Attachment #2: Type: text/html, Size: 2117 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 10:50 ` Rémi Dewitte
@ 2009-02-17 10:56 ` Mark Shinwell
2009-02-17 11:33 ` Jon Harrop
1 sibling, 0 replies; 21+ messages in thread
From: Mark Shinwell @ 2009-02-17 10:56 UTC (permalink / raw)
To: Rémi Dewitte; +Cc: caml-list
On Tue, Feb 17, 2009 at 11:50:44AM +0100, Rémi Dewitte wrote:
> Hello !
>
> Many thanks all for your answers !
>
> Managing to have the almost same performance whether in mutithreaded
> environment or not (even not using threads for this particular task) is
> something I would like to have anyway.
>
> I'll give a try to big buffers using Using.read. Any example code around ?
> And then why not try iao !
Try something straightforward to start with like reading the file just
one line at a time? Especially if the lines are long this should give a
good improvement.
Mark
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 10:50 ` Rémi Dewitte
2009-02-17 10:56 ` Mark Shinwell
@ 2009-02-17 11:33 ` Jon Harrop
1 sibling, 0 replies; 21+ messages in thread
From: Jon Harrop @ 2009-02-17 11:33 UTC (permalink / raw)
To: caml-list
On Tuesday 17 February 2009 10:50:44 Rémi Dewitte wrote:
> Memory mapping of the file could be done using BigArray or do I have to
> write C code ?
You can memory map files very easily entirely from within OCaml.
This was actually covered in the OCaml Journal article about OpenGL 2, which
used file mapping as an easy way to load texture maps. First, you open the
file to create a file descriptor:
try_finally (Unix.openfile file [] 777)
Then you map the file to create a big array:
(fun desc ->
let source = Array1.map_file desc int8_signed c_layout false (-1) in
Then you can do something with the big array, like copy it into an ordinary
string:
String.init (Array1.dim source) (fun i -> Char.chr source.{i}))
Finally, you close the file:
Unix.close
Note that I have used try_finally and String.init functions from my own stdlib
but their purpose and use should be obvious.
You probably just want to replace read_char with a function that increments a
counter and reads from the array, with the whole parser inside the
try_finally.
--
Dr Jon Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/?e
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 10:07 ` Sylvain Le Gall
2009-02-17 10:26 ` [Caml-list] " Mark Shinwell
@ 2009-02-17 12:20 ` Yaron Minsky
2009-02-17 12:26 ` Rémi Dewitte
2009-02-17 17:14 ` Sylvain Le Gall
1 sibling, 2 replies; 21+ messages in thread
From: Yaron Minsky @ 2009-02-17 12:20 UTC (permalink / raw)
To: Sylvain Le Gall; +Cc: caml-list
[-- Attachment #1: Type: text/plain, Size: 1824 bytes --]
Interestingly, this probably has nothing to do with the size of the buffer.
input_char actually acquires and releases a lock for every single call,
whether or not an underlying system call is required to fill the buffer.
This has always struck me as an odd aspect of the in/out channel
implementation, and means that IO is a lot more expensive in a threaded
context than it should be.
At Jane Street, performance-sensitive code tends to use other libraries that
we've built directly on top of file descriptors that batches the IO and
doesn't require constant lock acquisition.
y
On Tue, Feb 17, 2009 at 5:07 AM, Sylvain Le Gall <sylvain@le-gall.net>wrote:
> On 17-02-2009, Rémi Dewitte <remi@gide.net> wrote:
> >
> > test.csv is a 21mo file with ~13k rows and a thousands of columns on a
> 15rp=
> > m
> > disk.
> >
> > ocaml version : 3.11.0
> >
>
> You are using input_char and standard IO channel. This is a good choice
> for non-threaded program. But in your case, I will use Unix.read with a
> big buffer (32KB to 4MB) and change your program to use it. As
> benchmarked by John Harrop, you are spending most of your time in
> caml_enter|leave_blocking section. I think it comes from reading using
> std IO channel which use 4k buffer. Using a bigger buffer will allow
> less call to this two functions (but you won't win time at the end, I
> think you will just reduce the difference between non-threaded and
> threaded code).
>
> Regards
> Sylvain Le Gall
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
[-- Attachment #2: Type: text/html, Size: 2617 bytes --]
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-17 12:20 ` Yaron Minsky
@ 2009-02-17 12:26 ` Rémi Dewitte
2009-02-17 17:14 ` Sylvain Le Gall
1 sibling, 0 replies; 21+ messages in thread
From: Rémi Dewitte @ 2009-02-17 12:26 UTC (permalink / raw)
To: yminsky; +Cc: Sylvain Le Gall, caml-list
[-- Attachment #1.1: Type: text/plain, Size: 2424 bytes --]
Not using channels with either file descriptors or bigarray works well in my
case.
Good to know when working with ocaml to take care of channels ;) !
Rémi
2009/2/17 Yaron Minsky <yminsky@gmail.com>
> Interestingly, this probably has nothing to do with the size of the
> buffer. input_char actually acquires and releases a lock for every single
> call, whether or not an underlying system call is required to fill the
> buffer. This has always struck me as an odd aspect of the in/out channel
> implementation, and means that IO is a lot more expensive in a threaded
> context than it should be.
>
> At Jane Street, performance-sensitive code tends to use other libraries
> that we've built directly on top of file descriptors that batches the IO and
> doesn't require constant lock acquisition.
>
> y
>
>
> On Tue, Feb 17, 2009 at 5:07 AM, Sylvain Le Gall <sylvain@le-gall.net>wrote:
>
>> On 17-02-2009, Rémi Dewitte <remi@gide.net> wrote:
>> >
>> > test.csv is a 21mo file with ~13k rows and a thousands of columns on a
>> 15rp=
>> > m
>> > disk.
>> >
>> > ocaml version : 3.11.0
>> >
>>
>> You are using input_char and standard IO channel. This is a good choice
>> for non-threaded program. But in your case, I will use Unix.read with a
>> big buffer (32KB to 4MB) and change your program to use it. As
>> benchmarked by John Harrop, you are spending most of your time in
>> caml_enter|leave_blocking section. I think it comes from reading using
>> std IO channel which use 4k buffer. Using a bigger buffer will allow
>> less call to this two functions (but you won't win time at the end, I
>> think you will just reduce the difference between non-threaded and
>> threaded code).
>>
>> Regards
>> Sylvain Le Gall
>>
>> _______________________________________________
>> Caml-list mailing list. Subscription management:
>> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
>> Archives: http://caml.inria.fr
>> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
>> Bug reports: http://caml.inria.fr/bin/caml-bugs
>>
>
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
>
[-- Attachment #1.2: Type: text/html, Size: 3792 bytes --]
[-- Attachment #2: transi2.ml --]
[-- Type: text/x-ocaml, Size: 3737 bytes --]
(* open ExtLib *)
(** Slithly modified copy from module CSV *)
exception Bad_CSV_file of string
type state_t = StartField
| InUnquotedField
| InQuotedField
| InQuotedFieldAfterQuote
let load_rows ?(separator = ',') ?(nread = -1) f file =
let nr = ref 0 in
let row = ref [] in (* Current row. *)
let field = ref [] in (* Current field. *)
let state = ref StartField in (* Current state. *)
let end_of_field () =
let field_list = List.rev !field in
let field_len = List.length field_list in
let field_str = String.create field_len in
let rec loop i = function
[] -> ()
| x :: xs ->
field_str.[i] <- x;
loop (i+1) xs
in
loop 0 field_list;
row := (Some field_str) :: !row;
field := [];
state := StartField
in
let empty_field () =
row := None :: !row;
field := [];
state := StartField
in
let end_of_row () =
let row_list = List.rev !row in
f row_list;
row := [];
state := StartField;
nr := !nr + 1;
in
let process c =
if c != '\r' then ( (* Always ignore \r characters. *)
match !state with
StartField -> (* Expecting quote or other char. *)
if c = '"' then (
state := InQuotedField;
field := []
) else if c = separator then (* Empty field. *)
empty_field ()
else if c = '\n' then ( (* Empty field, end of row. *)
empty_field ();
end_of_row ()
) else (
state := InUnquotedField;
field := [c]
)
| InUnquotedField -> (* Reading chars to end of field. *)
if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else
field := c :: !field
| InQuotedField -> (* Reading chars to end of field. *)
if c = '"' then
state := InQuotedFieldAfterQuote
else
field := c :: !field
| InQuotedFieldAfterQuote ->
if c = '"' then ( (* Doubled quote. *)
field := c :: !field;
state := InQuotedField
) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
field := '\000' :: !field;
state := InQuotedField
) else if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else ( (* Bad single quote in field. *)
field := c :: '"' :: !field;
state := InQuotedField
)
) (* end of match *)
in
let continue = ref true in
let file_in = Unix.openfile file [Unix.O_RDONLY] 0o640 in
let end_processing () =
continue := false;
try Unix.close file_in with _ -> ();
(match !state with
| StartField ->
if !row <> [] then
( empty_field (); end_of_row () )
| InUnquotedField | InQuotedFieldAfterQuote ->
end_of_field (); end_of_row ()
| InQuotedField ->
raise (Bad_CSV_file "Missing end quote after quoted field.")
)
in
let buffer_length = 2 * 1024 * 1024 in
let buffer = String.make buffer_length '\000' in
let process_buffer l =
(* for i = 0 to l do *)
let ii = ref 0 in
while (!continue) && (!ii) <= l do
let i = !ii in
process buffer.[i];
ii := i + 1;
if( nread > 0 && !nr = nread ) then end_processing () else ()
done
in
while !(continue)
do
let n = Unix.read file_in buffer 0 buffer_length in
if (n > 0 )
then process_buffer n
else end_processing ()
done
let run_threaded f = Thread.create (fun () -> f (); Thread.exit ()) ()
let t1 = load_rows (fun _ -> ()) "test.csv"
let t2 = load_rows (fun _ -> ()) "test2.csv"
let t3 = load_rows (fun _ -> ()) "test3.csv"
[-- Attachment #3: transimm.ml --]
[-- Type: text/x-ocaml, Size: 3514 bytes --]
(* open ExtLib *)
open Bigarray
(** Slithly modified copy from module CSV *)
exception Bad_CSV_file of string
type state_t = StartField
| InUnquotedField
| InQuotedField
| InQuotedFieldAfterQuote
let load_rows ?(separator = ',') ?(nread = -1) f file =
let nr = ref 0 in
let row = ref [] in (* Current row. *)
let field = ref [] in (* Current field. *)
let state = ref StartField in (* Current state. *)
let end_of_field () =
let field_list = List.rev !field in
let field_len = List.length field_list in
let field_str = String.create field_len in
let rec loop i = function
[] -> ()
| x :: xs ->
field_str.[i] <- x;
loop (i+1) xs
in
loop 0 field_list;
row := (Some field_str) :: !row;
field := [];
state := StartField
in
let empty_field () =
row := None :: !row;
field := [];
state := StartField
in
let end_of_row () =
let row_list = List.rev !row in
f row_list;
row := [];
state := StartField;
nr := !nr + 1;
in
let process c =
if c != '\r' then ( (* Always ignore \r characters. *)
match !state with
StartField -> (* Expecting quote or other char. *)
if c = '"' then (
state := InQuotedField;
field := []
) else if c = separator then (* Empty field. *)
empty_field ()
else if c = '\n' then ( (* Empty field, end of row. *)
empty_field ();
end_of_row ()
) else (
state := InUnquotedField;
field := [c]
)
| InUnquotedField -> (* Reading chars to end of field. *)
if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else
field := c :: !field
| InQuotedField -> (* Reading chars to end of field. *)
if c = '"' then
state := InQuotedFieldAfterQuote
else
field := c :: !field
| InQuotedFieldAfterQuote ->
if c = '"' then ( (* Doubled quote. *)
field := c :: !field;
state := InQuotedField
) else if c = '0' then ( (* Quote-0 is ASCII NUL. *)
field := '\000' :: !field;
state := InQuotedField
) else if c = separator then (* End of field. *)
end_of_field ()
else if c = '\n' then ( (* End of field and end of row. *)
end_of_field ();
end_of_row ()
) else ( (* Bad single quote in field. *)
field := c :: '"' :: !field;
state := InQuotedField
)
) (* end of match *)
in
let file_in = Unix.openfile file [Unix.O_RDONLY] 0o640 in
let end_processing () =
try Unix.close file_in with _ -> ();
(match !state with
| StartField ->
if !row <> [] then
( empty_field (); end_of_row () )
| InUnquotedField | InQuotedFieldAfterQuote ->
end_of_field (); end_of_row ()
| InQuotedField ->
raise (Bad_CSV_file "Missing end quote after quoted field.")
)
in
let mmap = Bigarray.Array1.map_file file_in Bigarray.char Bigarray.c_layout false (-1) in
let l = (Bigarray.Array1.dim mmap) in
let continue = ref true in
let i = ref 0 in
while !continue do
process (Array1.(*unsafe_*)get mmap !i);
i := !i + 1;
continue := (nread < 0 || !nr < nread ) && !i < l
done;
end_processing ()
;;
let run_threaded f = Thread.create (fun () -> f (); Thread.exit ());;
load_rows (fun _ -> ()) "test.csv" ;;
load_rows (fun _ -> ()) "test2.csv";;
load_rows (fun _ -> ()) "test3.csv";;
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Caml-list] Re: Threads performance issue.
2009-02-16 16:32 ` Sylvain Le Gall
@ 2009-02-17 13:52 ` Frédéric Gava
0 siblings, 0 replies; 21+ messages in thread
From: Frédéric Gava @ 2009-02-17 13:52 UTC (permalink / raw)
Cc: caml-list
>> I would like to read two files in two different threads.
>>
>> I have made a first version reading the first then the second and it takes
>> 2.8s (native).
>>
>> I decided to make a threaded version and before any use of thread I realize=
>> d
>> that just linking no even using it to the threads library makes my first
>> version of the program to run in 12s !
This kind of trick can work when files are on different discs due to two
I/O calls (even using blocking reading). But in a single thread using
non-blocking reading should also work but it is more difficult to write.
FG
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Threads performance issue.
2009-02-17 12:20 ` Yaron Minsky
2009-02-17 12:26 ` Rémi Dewitte
@ 2009-02-17 17:14 ` Sylvain Le Gall
1 sibling, 0 replies; 21+ messages in thread
From: Sylvain Le Gall @ 2009-02-17 17:14 UTC (permalink / raw)
To: caml-list
Hello,
On 17-02-2009, Yaron Minsky <yminsky@gmail.com> wrote:
>
> Interestingly, this probably has nothing to do with the size of the buffer.
> input_char actually acquires and releases a lock for every single call,
> whether or not an underlying system call is required to fill the buffer.
> This has always struck me as an odd aspect of the in/out channel
> implementation, and means that IO is a lot more expensive in a threaded
> context than it should be.
>
>
> On Tue, Feb 17, 2009 at 5:07 AM, Sylvain Le Gall <sylvain@le-gall.net>wrote=
>>
>> You are using input_char and standard IO channel. This is a good choice
>> for non-threaded program. But in your case, I will use Unix.read with a
>> big buffer (32KB to 4MB) and change your program to use it. As
>> benchmarked by John Harrop, you are spending most of your time in
>> caml_enter|leave_blocking section. I think it comes from reading using
>> std IO channel which use 4k buffer. Using a bigger buffer will allow
>> less call to this two functions (but you won't win time at the end, I
>> think you will just reduce the difference between non-threaded and
>> threaded code).
>>
You are probably true concerning the fact that it has nothing to do with
size of the buffer. I am just mixing two kind of optimization. Anyway, I
think even if the size is not important, using Unix.read + file
descriptor should do the trick.
Thanks for your detailed explanation.
Regards
Sylvain Le Gall
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2009-02-17 17:14 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-02-16 15:15 Threads performance issue Rémi Dewitte
2009-02-16 15:28 ` [Caml-list] " Michał Maciejewski
2009-02-16 15:32 ` Rémi Dewitte
2009-02-16 15:42 ` David Allsopp
2009-02-16 16:07 ` Rémi Dewitte
2009-02-16 16:32 ` Sylvain Le Gall
2009-02-17 13:52 ` [Caml-list] " Frédéric Gava
2009-02-16 16:47 ` [Caml-list] " Yaron Minsky
2009-02-16 17:37 ` Rémi Dewitte
2009-02-17 7:40 ` Rémi Dewitte
2009-02-17 8:59 ` Mark Shinwell
2009-02-17 9:09 ` Rémi Dewitte
2009-02-17 9:53 ` Jon Harrop
2009-02-17 10:07 ` Sylvain Le Gall
2009-02-17 10:26 ` [Caml-list] " Mark Shinwell
2009-02-17 10:50 ` Rémi Dewitte
2009-02-17 10:56 ` Mark Shinwell
2009-02-17 11:33 ` Jon Harrop
2009-02-17 12:20 ` Yaron Minsky
2009-02-17 12:26 ` Rémi Dewitte
2009-02-17 17:14 ` Sylvain Le Gall
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox