caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* Re: [Caml-list] Question on writing efficient Ocaml.
       [not found] <15946.213.30.139.86.1167315231.squirrel@webmail.nerim.net>
@ 2006-12-28 16:03 ` Ian Oversby
  2006-12-28 17:00   ` Richard Jones
  2006-12-28 22:23   ` Jon Harrop
  0 siblings, 2 replies; 17+ messages in thread
From: Ian Oversby @ 2006-12-28 16:03 UTC (permalink / raw)
  To: oandrieu; +Cc: caml-list

Hi Olivier,

Thanks for the response.

> > Hi,
>
> > I've written some Ocaml code to solve the problem of placing 8 queens on 
>a
> > board so that none of them attack each-other.  I've also written a C++
> > equivalent which is running much more quickly than the Ocaml.  I assume
> > I've
> > made some basic errors with the Ocaml - does anyone have any suggestions
> > as
> > to what?
>
>there is room for improvement: for instance the type definitions of posn
>and board at the very beginning of the program introduce some unneeded
>boxing of values.

Does this mean that unboxing is inefficient in OCaml?  I've written an 
alternative version of the C++ that returns NULL instead of out of bound 
values which was close to the same speed so it would be a little 
disappointing if I couldn't achieve something similar in OCaml with Some / 
None.  Let me try to convert the OCaml to use out of bounds board values 
instead to see if that solves the speed problem.

>You might want to compare with this solution of the queens problem in 
>ocaml:
>   http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml

I've written a queens solver along the same lines which is much faster than 
my other example as it makes many fewer calls and constructs fewer (and 
simpler) boards.

> > I compiled the Ocaml with the following command:
> >
> > ocamlopt -noassert -unsafe -ccopt -O3 -ccopt -fomit-frame-pointer q.ml 
>-o
> > q.exe
>
>the "-ccopt -O3 -ccopt -fomit-frame-pointer" are completely pointless: the
>ocaml compiler does not generate C code, it generates asm !

Well, that is certainly good to know.  Thanks very much :)

>--
>   Olivier

Ian

_________________________________________________________________
MSN Hotmail is evolving – check out the new Windows Live Mail 
http://ideas.live.com


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 16:03 ` [Caml-list] Question on writing efficient Ocaml Ian Oversby
@ 2006-12-28 17:00   ` Richard Jones
  2006-12-28 22:23   ` Jon Harrop
  1 sibling, 0 replies; 17+ messages in thread
From: Richard Jones @ 2006-12-28 17:00 UTC (permalink / raw)
  To: Ian Oversby; +Cc: oandrieu, caml-list

On Thu, Dec 28, 2006 at 04:03:55PM +0000, Ian Oversby wrote:
> Does this mean that unboxing is inefficient in OCaml?  I've written an 
> alternative version of the C++ that returns NULL instead of out of bound 
> values which was close to the same speed so it would be a little 
> disappointing if I couldn't achieve something similar in OCaml with Some / 
> None.

It's not so much that boxing/unboxing is inefficient in OCaml, but
rather that ocamlopt compiles exactly what you ask it to.  If you ask
it to use a box, it uses a box!  (Well, mostly ...)

See: http://caml.inria.fr/pub/old_caml_site/ocaml/numerical.html
in particular the note about Gallium.

Rich.

-- 
Richard Jones, CTO Merjis Ltd.
Merjis - web marketing and technology - http://merjis.com
Internet Marketing and AdWords courses - http://merjis.com/courses - NEW!
Merjis blog - http://blog.merjis.com - NEW!


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 16:03 ` [Caml-list] Question on writing efficient Ocaml Ian Oversby
  2006-12-28 17:00   ` Richard Jones
@ 2006-12-28 22:23   ` Jon Harrop
  2006-12-29  9:42     ` Ian Oversby
  1 sibling, 1 reply; 17+ messages in thread
From: Jon Harrop @ 2006-12-28 22:23 UTC (permalink / raw)
  To: caml-list

On Thursday 28 December 2006 16:03, Ian Oversby wrote:
> Does this mean that unboxing is inefficient in OCaml?

Yes. However, you've had to go out of your way to make the OCaml slow in this 
case.

> I've written an 
> alternative version of the C++ that returns NULL instead of out of bound
> values which was close to the same speed so it would be a little
> disappointing if I couldn't achieve something similar in OCaml with Some /
> None.

You would be better off focusing on higher-level optimisations, like 
algorithmic optimisations.

> >You might want to compare with this solution of the queens problem in
> >ocaml:
> >   http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/queens.ml
>
> I've written a queens solver along the same lines which is much faster than
> my other example as it makes many fewer calls and constructs fewer (and
> simpler) boards.

Why are you optimising this version if you already have a faster one?

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
Objective CAML for Scientists
http://www.ffconsultancy.com/products/ocaml_for_scientists


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 22:23   ` Jon Harrop
@ 2006-12-29  9:42     ` Ian Oversby
  0 siblings, 0 replies; 17+ messages in thread
From: Ian Oversby @ 2006-12-29  9:42 UTC (permalink / raw)
  To: caml-list

>From: Jon Harrop <jon@ffconsultancy.com>
>Why are you optimising this version if you already have a faster one?

I was hoping to compare similar approaches in Scheme, Ocaml and C++ in much 
the same way as the language shootout does.  I don't really have a desperate 
need for a fast queens solver ;-)  Anyway, thanks for pointing out the 
algorithmic difference between the C++ and the Ocaml.  I'll see if I can 
track down what causes this.

Cheers,

Ian

_________________________________________________________________
MSN Hotmail is evolving – check out the new Windows Live Mail 
http://ideas.live.com


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2007-01-06  2:27             ` Martin Jambon
@ 2007-01-08 22:23               ` Nathaniel Gray
  0 siblings, 0 replies; 17+ messages in thread
From: Nathaniel Gray @ 2007-01-08 22:23 UTC (permalink / raw)
  To: Martin Jambon; +Cc: brogoff, caml-list

Thanks to everybody for the discussion of phantom types.  I guess
modules are unavoidable then.  Too bad...  :^(

On 1/5/07, Martin Jambon <martin.jambon@ens-lyon.org> wrote:
> On Fri, 5 Jan 2007, brogoff wrote:
>
> > On Fri, 5 Jan 2007, Nathaniel Gray wrote:
> >> On 12/29/06, Mattias Engdegård <mattias@virtutech.se> wrote:
> >>> Is there a reason for this? To my innocent eyes, code like
> >>>
> >>>   type length = Length of int
> >>>
> >>> looks quite reasonable and could be useful at times.
> >>
> >> I agree.  Sadly, the ocaml devs don't.
> >>
> >> http://caml.inria.fr/mantis/view.php?id=3978
> >>
> >> As Xavier points out, one can use modules to hide basic types, but
> >> this is pretty clumsy in practice.  There's rumored to be a solution
> >> using phantom types, but my attempts don't work:
> >
> > You need to use modules to make phantom types work. Something like this
> >
> > module type BOINK =
> >  sig
> >    type 'a t
> >    val inj : int -> 'a t
> >    val prj : 'a t -> int
> >    val plus : 'a t -> 'a t -> 'a t
> >  end;;
> >
> > module Boink : BOINK =
> >  struct
> >    type 'a t = int
> >    let inj n = n
> >    let prj t = t
> >    let plus x y = x + y
> >  end;;
> >
> > let f : string Boink.t = inj 20;;
> > let g : int Boink.t = inj 30;;
> >
> > Boink.plus f g;;
> >
> > I didn't compile that, but you get the idea...
>
> In case anyone finds it useful, I have this code which is ready to use
> (copy the files it into your project):
>
>    http://martin.jambon.free.fr/ocaml.html#opaque
>
> It works only for strings and ints.
> You can write:
>
> open Opaque
> let x : [`Year] int_t = int_t 2007
> let next_year x : [`Year] int_t = int_t (t_int x + 1)
>
> It gives you:
> val x : [ `Year ] Opaque.int_t
> val next_year : 'a Opaque.int_t -> [ `Year ] Opaque.int_t
>
> Note that we need one more type annotation if we want to get the
> following signature:
> val next_year : [ `Year ] Opaque.int_t -> [ `Year ] Opaque.int_t
>
> or we can just use "successor" which is equivalent to "succ":
> val successor : 'a Opaque.int_t -> 'a Opaque.int_t
>
>
> As you can see, things can get pretty ugly, but I found this technique
> useful to avoid confusion between identifiers that identify different
> types of objects. Not in my average "hello world" script.
>
>
> Martin
>
> --
> Martin Jambon
> http://martin.jambon.free.fr
>


-- 
>>>-- Nathaniel Gray -- Caltech Computer Science ------>
>>>-- Mojave Project -- http://mojave.cs.caltech.edu -->


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2007-01-06  1:15           ` brogoff
@ 2007-01-06  2:27             ` Martin Jambon
  2007-01-08 22:23               ` Nathaniel Gray
  0 siblings, 1 reply; 17+ messages in thread
From: Martin Jambon @ 2007-01-06  2:27 UTC (permalink / raw)
  To: brogoff; +Cc: Nathaniel Gray, caml-list

[-- Attachment #1: Type: TEXT/PLAIN, Size: 2040 bytes --]

On Fri, 5 Jan 2007, brogoff wrote:

> On Fri, 5 Jan 2007, Nathaniel Gray wrote:
>> On 12/29/06, Mattias Engdegård <mattias@virtutech.se> wrote:
>>> Is there a reason for this? To my innocent eyes, code like
>>>
>>>   type length = Length of int
>>>
>>> looks quite reasonable and could be useful at times.
>>
>> I agree.  Sadly, the ocaml devs don't.
>>
>> http://caml.inria.fr/mantis/view.php?id=3978
>>
>> As Xavier points out, one can use modules to hide basic types, but
>> this is pretty clumsy in practice.  There's rumored to be a solution
>> using phantom types, but my attempts don't work:
>
> You need to use modules to make phantom types work. Something like this
>
> module type BOINK =
>  sig
>    type 'a t
>    val inj : int -> 'a t
>    val prj : 'a t -> int
>    val plus : 'a t -> 'a t -> 'a t
>  end;;
>
> module Boink : BOINK =
>  struct
>    type 'a t = int
>    let inj n = n
>    let prj t = t
>    let plus x y = x + y
>  end;;
>
> let f : string Boink.t = inj 20;;
> let g : int Boink.t = inj 30;;
>
> Boink.plus f g;;
>
> I didn't compile that, but you get the idea...

In case anyone finds it useful, I have this code which is ready to use
(copy the files it into your project):

   http://martin.jambon.free.fr/ocaml.html#opaque

It works only for strings and ints.
You can write:

open Opaque
let x : [`Year] int_t = int_t 2007
let next_year x : [`Year] int_t = int_t (t_int x + 1)

It gives you:
val x : [ `Year ] Opaque.int_t
val next_year : 'a Opaque.int_t -> [ `Year ] Opaque.int_t

Note that we need one more type annotation if we want to get the 
following signature:
val next_year : [ `Year ] Opaque.int_t -> [ `Year ] Opaque.int_t

or we can just use "successor" which is equivalent to "succ":
val successor : 'a Opaque.int_t -> 'a Opaque.int_t


As you can see, things can get pretty ugly, but I found this technique 
useful to avoid confusion between identifiers that identify different 
types of objects. Not in my average "hello world" script.


Martin

--
Martin Jambon
http://martin.jambon.free.fr

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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2007-01-06  0:52         ` Nathaniel Gray
  2007-01-06  1:01           ` Philippe Wang
@ 2007-01-06  1:15           ` brogoff
  2007-01-06  2:27             ` Martin Jambon
  1 sibling, 1 reply; 17+ messages in thread
From: brogoff @ 2007-01-06  1:15 UTC (permalink / raw)
  To: Nathaniel Gray; +Cc: caml-list

On Fri, 5 Jan 2007, Nathaniel Gray wrote:
> On 12/29/06, Mattias Engdegård <mattias@virtutech.se> wrote:
> > Is there a reason for this? To my innocent eyes, code like
> >
> >   type length = Length of int
> >
> > looks quite reasonable and could be useful at times.
>
> I agree.  Sadly, the ocaml devs don't.
>
> http://caml.inria.fr/mantis/view.php?id=3978
>
> As Xavier points out, one can use modules to hide basic types, but
> this is pretty clumsy in practice.  There's rumored to be a solution
> using phantom types, but my attempts don't work:

You need to use modules to make phantom types work. Something like this

module type BOINK =
  sig
    type 'a t
    val inj : int -> 'a t
    val prj : 'a t -> int
    val plus : 'a t -> 'a t -> 'a t
  end;;

module Boink : BOINK =
  struct
    type 'a t = int
    let inj n = n
    let prj t = t
    let plus x y = x + y
  end;;

let f : string Boink.t = inj 20;;
let g : int Boink.t = inj 30;;

Boink.plus f g;;

I didn't compile that, but you get the idea...

-- Brian


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2007-01-06  0:52         ` Nathaniel Gray
@ 2007-01-06  1:01           ` Philippe Wang
  2007-01-06  1:15           ` brogoff
  1 sibling, 0 replies; 17+ messages in thread
From: Philippe Wang @ 2007-01-06  1:01 UTC (permalink / raw)
  To: Nathaniel Gray; +Cc: caml-list


Le 6 janv. 07 à 01:52, Nathaniel Gray a écrit :

> As Xavier points out, one can use modules to hide basic types, but
> this is pretty clumsy in practice.  There's rumored to be a solution
> using phantom types, but my attempts don't work:
>
> # type 'a boink = int;;
> type 'a boink = int
> # let f = (20 : string boink);;
> val f : string boink = 20
> # let g = (30 : int boink);;
> val g : int boink = 30
> # f;;
> - : string boink = 20
> # g;;
> - : int boink = 30
> # f + g;;
> - : int = 50

Hmmm... the expression
> f + g;;
becomes an int because ( + ) : int -> int -> int

If you write :

((+) : int -> int boink -> string boink) f g

instead, it may do what you meant to do...

Cheers,

Philippe Wang
    mail(at)philippewang.info

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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-29 11:15       ` Mattias Engdegård
@ 2007-01-06  0:52         ` Nathaniel Gray
  2007-01-06  1:01           ` Philippe Wang
  2007-01-06  1:15           ` brogoff
  0 siblings, 2 replies; 17+ messages in thread
From: Nathaniel Gray @ 2007-01-06  0:52 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: garrigue, caml-list, skaller

On 12/29/06, Mattias Engdegård <mattias@virtutech.se> wrote:
> >In general, I wouldn't explicitly advise against using single
> >constructor type definitions for documentation, except when the
> >argument is a single int or float and performance matters.
>
> Is there a reason for this? To my innocent eyes, code like
>
>   type length = Length of int
>
> looks quite reasonable and could be useful at times.

I agree.  Sadly, the ocaml devs don't.

http://caml.inria.fr/mantis/view.php?id=3978

As Xavier points out, one can use modules to hide basic types, but
this is pretty clumsy in practice.  There's rumored to be a solution
using phantom types, but my attempts don't work:

# type 'a boink = int;;
type 'a boink = int
# let f = (20 : string boink);;
val f : string boink = 20
# let g = (30 : int boink);;
val g : int boink = 30
# f;;
- : string boink = 20
# g;;
- : int boink = 30
# f + g;;
- : int = 50

> If binary compatibility with old OCaml releases would be the reason,
> is there a policy stating when such ABI details can be changed?

The ocaml devs have been quite clear that the ABI can change at any
time, so that's not the reason.

Cheers,
-n8

-- 
>>>-- Nathaniel Gray -- Caltech Computer Science ------>
>>>-- Mojave Project -- http://mojave.cs.caltech.edu -->


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-29  2:07 ` Jon Harrop
@ 2007-01-03 16:43   ` Serge Aleynikov
  0 siblings, 0 replies; 17+ messages in thread
From: Serge Aleynikov @ 2007-01-03 16:43 UTC (permalink / raw)
  To: Jon Harrop, oversby; +Cc: caml-list

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

Here's a slightly modified version of the algorithm that finds all 
solutions for the N-Queens problem implemented in C/C++/OCaml/Erlang.

$ wc -l nqueens.{c,cpp,ml,erl}
   93 nqueens.c
  109 nqueens.cpp
   61 nqueens.ml
   61 nqueens.erl

On my host timing is as follows:

$ make run   # 8 queens, 1000 times
# Program               Time
./queens_c:             0.96
./queens_cpp:           1.16
./queens_ml:            0.77
./queens_ml.bcode:      22.55
erl -noshell -run nqueens main 8 1000 -s erlang halt -- :  8.56

Note that Erlang code is natively compiled.  Without the native flag it 
gives about the same time as queens_ml.bcode.  I also have a parallel 
version of Erlang implementation of this algorithm that in SMP mode 
scales linearly with the number of CPUs, but since this "number 
crunching" problem domain is not for meant for Erlang, I haven't 
included it in this email.

If we use optimization (-O3 for C/C++ and -unsafe for OCaml) the timing 
picture is slightly different.

Serge

Jon Harrop wrote:
> On Thursday 28 December 2006 11:42, Ian Oversby wrote:
>> I've written some Ocaml code to solve the problem of placing 8 queens on a
>> board so that none of them attack each-other.
> 
> Your program is very verbose: many times longer than is necessary. Most of 
> this verbosity can be attributed to performing various boxing, unboxing and 
> allocation tasks that are superfluous and simply slow the code down. However, 
> profiling suggests that you're also using a different algorithm in OCaml as 
> the core functions are called many more times in the OCaml than in the C++.
> 
> Contrast your code with a minimal program to solve the n-queens problem in 
> OCaml:
> 
> let rec unthreatened (x1, y1) (x2, y2) =
>   x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1;;
> 
> let rec search n f qs ps =
>   if length qs = n then f qs else
>     iter (fun q -> search n f (q::qs) (filter (unthreatened q) ps)) ps;;
> 
> let ps = rev (flatten (init n (fun i -> init n (fun j -> i, j))))
> search n f [] ps
> 
> This program starts with a list of all board positions "ps" and simply filters 
> out threatened positions as queens are added. Performance is poor compared to 
> your C++:
> 
> 0.625s C++ (130 LOC)
> 4.466s OCaml (28 LOC)
> 
> My first solution is written for brevity and not performance so it is still 3x 
> slower than the C++:
> 
> The core of the program is just this:
> 
> open List;;
> 
> let print_board n queens =
>   for y=0 to n-1 do
>     for x=0 to n-1 do
>       print_string (if mem (x, y) queens then "Q" else ".")
>     done;
>     print_newline()
>   done;
>   print_newline();;
> 
> let rec fold_n2 f accu x y n =
>   if y=n then accu else
>     if x=n then fold_n2 f accu 0 (y + 1) n else
>       fold_n2 f (f accu x y) (x + 1) y n;;
> 
> let unthreatened (x1, y1) (x2, y2) =
>   x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1;;
> 
> let rec search n f queens () x y =
>   if for_all (unthreatened (x, y)) queens then
>     if length queens = n - 1 then f ((x, y) :: queens) else
>       fold_n2 (search n f ((x, y) :: queens)) () (x + 1) y n;;
> 
> then I wrote this to search once and print and then search a given number of 
> times (for benchmarking):
> 
> exception Queens of (int * int) list;;
> 
> let _ =
>   let n = 8 in
>   let f qs = raise (Queens qs) in
>   (try search n f [] () 0 0 with Queens qs -> print_board n qs);
>   match Sys.argv with
>   | [|_; reps|] ->
>       for rep=2 to int_of_string reps do
> 	try search n (fun _ -> raise Exit) [] () 0 0 with Exit -> ()
>       done
>   | _ -> ()
> 
> The "fold_n2" and "search" functions are both polymorphic folds. The former 
> folds over a square array and the latter folds over all solutions to the 
> n-queens problem. The generality of the "search" function is not used in this 
> case, as I just print the first solution found and bail using an exception.
> 
> On my machine, 1000 solutions takes:
> 
> 0.625s C++ (130 LOC)
> 1.764s OCaml (30 LOC)
> 
> So OCaml is >4x more concise but 2.8x slower than C++.
> 
> Profiling shows that a lot of time is spent in List.for_all. We can roll this 
> ourselves to remove some polymorphism and improve performance:
> 
> let rec unthreatened x1 y1 = function
>   | (x2, y2) :: t ->
>       x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1 &&
> 	unthreatened x1 y1 t
>   | [] -> true;;
> 
> let rec search n f queens () x y =
>   if unthreatened x y queens then
>     if length queens = n - 1 then f ((x, y) :: queens) else
>       fold_n2 (search n f ((x, y) :: queens)) () (x + 1) y n;;
> 
> This gets the time down to:
> 
> 1.159s OCaml (33 LOC)
> 
> We can continue optimising by removing some generality. Let's turn the "fold" 
> into an "iter" so that "search" becomes monomorphic:
> 
> let rec iter_n2 f x y n =
>   if y<n then
>     if x=n then iter_n2 f 0 (y + 1) n else
>       (f x y;
>        iter_n2 f (x + 1) y n);;
> 
> let rec search n f queens x y =
>   if unthreatened x y queens then
>     if length queens = n - 1 then f ((x, y) :: queens) else
>       iter_n2 (search n f ((x, y) :: queens)) (x + 1) y n;;
> 
> Performance is improved even more, at the cost of generality.
> 
> 0.827s OCaml (34 LOC)
> 
> So OCaml is now only 30% slower whilst still being ~4x more concise.
> 


[-- Attachment #2: nqueens.tar.gz --]
[-- Type: application/x-gzip, Size: 3321 bytes --]

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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-29  6:05     ` Jacques Garrigue
@ 2006-12-29 11:15       ` Mattias Engdegård
  2007-01-06  0:52         ` Nathaniel Gray
  0 siblings, 1 reply; 17+ messages in thread
From: Mattias Engdegård @ 2006-12-29 11:15 UTC (permalink / raw)
  To: garrigue; +Cc: skaller, caml-list

>In general, I wouldn't explicitly advise against using single
>constructor type definitions for documentation, except when the
>argument is a single int or float and performance matters.

Is there a reason for this? To my innocent eyes, code like

  type length = Length of int

looks quite reasonable and could be useful at times.

If binary compatibility with old OCaml releases would be the reason,
is there a policy stating when such ABI details can be changed?


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-29  1:23 ` Andrej Bauer
@ 2006-12-29  9:58   ` Ian Oversby
  0 siblings, 0 replies; 17+ messages in thread
From: Ian Oversby @ 2006-12-29  9:58 UTC (permalink / raw)
  To: Andrej.Bauer, caml-list

>Dear Ian,
>
>your ocaml code is quite "unusual", and I must say I am not convinced your 
>C++ code is written optimally (but I don't know anything about C++).

I'm not convinced it is optimal either - I wrote the scheme version first
and then translated first into Ocaml and then to C++.  It is my first
piece of Ocaml (and hopefully not the last) which probably accounts for
the unusualness.

>I transcribed your C++ version to a reasonable ocaml version. Please 
>compare what you did with how I transliterated your C++ code (attached file 
>queen.ml). In particular, I use more obvious types (position is just 
>int*int, a board is a 2D array)
>
>Then I wrote my own ocaml version which mimicks your algorithm, except that 
>instead of creating new boards all the time, it just keeps a list of 
>current positions of queens (attached file queen2.ml).

Excellent.  Thanks very much.  I'll have a look at both of them and
see what I can learn.

>By the way, why are you placing 8 queens on the board, no matter how large 
>the board is? I thought the idea was to put n queens on a board of size n x 
>n.

Yes, good catch - it is a bug.  I didn't do sufficient testing.

[snip]

>Feel free to post this on your blog.

Thanks again.  I'll probably write a follow-up when I understand what
is going on.  Jon Harrop also uncovered an algorithmic difference between
the C++ and the Ocaml so I'll look into that too.

Cheers,

Ian

_________________________________________________________________
MSN Hotmail is evolving – check out the new Windows Live Mail 
http://ideas.live.com


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 17:13   ` skaller
@ 2006-12-29  6:05     ` Jacques Garrigue
  2006-12-29 11:15       ` Mattias Engdegård
  0 siblings, 1 reply; 17+ messages in thread
From: Jacques Garrigue @ 2006-12-29  6:05 UTC (permalink / raw)
  To: skaller; +Cc: caml-list

From: skaller <skaller@users.sourceforge.net>
> On Thu, 2006-12-28 at 16:26 +0000, Jon Harrop wrote:
> 
> > The code is also needlessly verbose and inefficient. There's no point in 
> > declaring sum types with one contructor:
> > 
> > type posn = Posn of int * int;;
> 
> Doesn't Ocaml optimise the constructor away in this case?

It's not really an optimization, rather that Posn being a constructor
with 2 arguments (rather than a constructor taking a pair as
argument), it ends up having exactly the same representation as a
pair. So there is no loss in efficiency here.

In general, I wouldn't explicitly advise against using single
constructor type definitions for documentation, except when the
argument is a single int or float and performance matters.

Jacques Garrigue


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 11:42 Ian Oversby
  2006-12-28 16:26 ` [Caml-list] " Jon Harrop
  2006-12-29  1:23 ` Andrej Bauer
@ 2006-12-29  2:07 ` Jon Harrop
  2007-01-03 16:43   ` Serge Aleynikov
  2 siblings, 1 reply; 17+ messages in thread
From: Jon Harrop @ 2006-12-29  2:07 UTC (permalink / raw)
  To: caml-list

On Thursday 28 December 2006 11:42, Ian Oversby wrote:
> I've written some Ocaml code to solve the problem of placing 8 queens on a
> board so that none of them attack each-other.

Your program is very verbose: many times longer than is necessary. Most of 
this verbosity can be attributed to performing various boxing, unboxing and 
allocation tasks that are superfluous and simply slow the code down. However, 
profiling suggests that you're also using a different algorithm in OCaml as 
the core functions are called many more times in the OCaml than in the C++.

Contrast your code with a minimal program to solve the n-queens problem in 
OCaml:

let rec unthreatened (x1, y1) (x2, y2) =
  x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1;;

let rec search n f qs ps =
  if length qs = n then f qs else
    iter (fun q -> search n f (q::qs) (filter (unthreatened q) ps)) ps;;

let ps = rev (flatten (init n (fun i -> init n (fun j -> i, j))))
search n f [] ps

This program starts with a list of all board positions "ps" and simply filters 
out threatened positions as queens are added. Performance is poor compared to 
your C++:

0.625s C++ (130 LOC)
4.466s OCaml (28 LOC)

My first solution is written for brevity and not performance so it is still 3x 
slower than the C++:

The core of the program is just this:

open List;;

let print_board n queens =
  for y=0 to n-1 do
    for x=0 to n-1 do
      print_string (if mem (x, y) queens then "Q" else ".")
    done;
    print_newline()
  done;
  print_newline();;

let rec fold_n2 f accu x y n =
  if y=n then accu else
    if x=n then fold_n2 f accu 0 (y + 1) n else
      fold_n2 f (f accu x y) (x + 1) y n;;

let unthreatened (x1, y1) (x2, y2) =
  x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1;;

let rec search n f queens () x y =
  if for_all (unthreatened (x, y)) queens then
    if length queens = n - 1 then f ((x, y) :: queens) else
      fold_n2 (search n f ((x, y) :: queens)) () (x + 1) y n;;

then I wrote this to search once and print and then search a given number of 
times (for benchmarking):

exception Queens of (int * int) list;;

let _ =
  let n = 8 in
  let f qs = raise (Queens qs) in
  (try search n f [] () 0 0 with Queens qs -> print_board n qs);
  match Sys.argv with
  | [|_; reps|] ->
      for rep=2 to int_of_string reps do
	try search n (fun _ -> raise Exit) [] () 0 0 with Exit -> ()
      done
  | _ -> ()

The "fold_n2" and "search" functions are both polymorphic folds. The former 
folds over a square array and the latter folds over all solutions to the 
n-queens problem. The generality of the "search" function is not used in this 
case, as I just print the first solution found and bail using an exception.

On my machine, 1000 solutions takes:

0.625s C++ (130 LOC)
1.764s OCaml (30 LOC)

So OCaml is >4x more concise but 2.8x slower than C++.

Profiling shows that a lot of time is spent in List.for_all. We can roll this 
ourselves to remove some polymorphism and improve performance:

let rec unthreatened x1 y1 = function
  | (x2, y2) :: t ->
      x1 <> x2 && y1 <> y2 && x2 - x1 <> y2 - y1 && x1 - y2 <> x2 - y1 &&
	unthreatened x1 y1 t
  | [] -> true;;

let rec search n f queens () x y =
  if unthreatened x y queens then
    if length queens = n - 1 then f ((x, y) :: queens) else
      fold_n2 (search n f ((x, y) :: queens)) () (x + 1) y n;;

This gets the time down to:

1.159s OCaml (33 LOC)

We can continue optimising by removing some generality. Let's turn the "fold" 
into an "iter" so that "search" becomes monomorphic:

let rec iter_n2 f x y n =
  if y<n then
    if x=n then iter_n2 f 0 (y + 1) n else
      (f x y;
       iter_n2 f (x + 1) y n);;

let rec search n f queens x y =
  if unthreatened x y queens then
    if length queens = n - 1 then f ((x, y) :: queens) else
      iter_n2 (search n f ((x, y) :: queens)) (x + 1) y n;;

Performance is improved even more, at the cost of generality.

0.827s OCaml (34 LOC)

So OCaml is now only 30% slower whilst still being ~4x more concise.

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
Objective CAML for Scientists
http://www.ffconsultancy.com/products/ocaml_for_scientists


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 11:42 Ian Oversby
  2006-12-28 16:26 ` [Caml-list] " Jon Harrop
@ 2006-12-29  1:23 ` Andrej Bauer
  2006-12-29  9:58   ` Ian Oversby
  2006-12-29  2:07 ` Jon Harrop
  2 siblings, 1 reply; 17+ messages in thread
From: Andrej Bauer @ 2006-12-29  1:23 UTC (permalink / raw)
  To: Ian Oversby, caml-list

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

Dear Ian,

your ocaml code is quite "unusual", and I must say I am not convinced 
your C++ code is written optimally (but I don't know anything about C++).

I transcribed your C++ version to a reasonable ocaml version. Please 
compare what you did with how I transliterated your C++ code (attached 
file queen.ml). In particular, I use more obvious types (position is 
just int*int, a board is a 2D array)

Then I wrote my own ocaml version which mimicks your algorithm, except 
that instead of creating new boards all the time, it just keeps a list 
of current positions of queens (attached file queen2.ml).

By the way, why are you placing 8 queens on the board, no matter how 
large the board is? I thought the idea was to put n queens on a board of 
size n x n.

The results are as follows. To compute 5000 times the solution for board 
size 8:
- your C++ takes:   4.055s
- your ocaml takes: 550s
- my transliteration queen.ml of your C++ takes: 46.603s
- my improved version queen2.ml takes: 25.483s

So, for a small board size your C++ wins over ocaml by a factor of 11.5 
(or just 6.28 if I am allowed to use a reasonable representation of 
boards). Also note how my ocaml transliteration is more than 10 times 
faster than yours (you did indeed write odd code). Someone on the list 
might suggest some further improvements to queen.ml.

Since your C++ copies around a lot of boards, it loses drastically 
against the improved ocaml version when the board size goes up. For 
example, at board size 100 your C++ is 10 times slower than my ocaml 
queen2.ml, but this comparison is not fair, as I changed the data 
structure. You should implement a C++ version that uses a list of queen 
positions to represent the board and compare against queen2.ml.

Oh, by the way, C++ loses in code size (but this is well known in general):
- your C++ is 161 lines (3849 bytes)
- the ocaml transliteration of your C++ is 79 lines (1870 bytes)
- my improved ocaml version is 58 lines (1472 butes).

Feel free to post this on your blog.

Best regards,

Andrej


[-- Attachment #2: queen.ml --]
[-- Type: application/x-forcedownload, Size: 1870 bytes --]

[-- Attachment #3: queen2.ml --]
[-- Type: application/x-forcedownload, Size: 1473 bytes --]

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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 16:26 ` [Caml-list] " Jon Harrop
@ 2006-12-28 17:13   ` skaller
  2006-12-29  6:05     ` Jacques Garrigue
  0 siblings, 1 reply; 17+ messages in thread
From: skaller @ 2006-12-28 17:13 UTC (permalink / raw)
  To: Jon Harrop; +Cc: caml-list

On Thu, 2006-12-28 at 16:26 +0000, Jon Harrop wrote:

> The code is also needlessly verbose and inefficient. There's no point in 
> declaring sum types with one contructor:
> 
> type posn = Posn of int * int;;

Doesn't Ocaml optimise the constructor away in this case?

-- 
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net


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

* Re: [Caml-list] Question on writing efficient Ocaml.
  2006-12-28 11:42 Ian Oversby
@ 2006-12-28 16:26 ` Jon Harrop
  2006-12-28 17:13   ` skaller
  2006-12-29  1:23 ` Andrej Bauer
  2006-12-29  2:07 ` Jon Harrop
  2 siblings, 1 reply; 17+ messages in thread
From: Jon Harrop @ 2006-12-28 16:26 UTC (permalink / raw)
  To: caml-list

On Thursday 28 December 2006 11:42, Ian Oversby wrote:
> Hi,
>
> Apologies if this is the wrong forum in which to ask this question.  Is
> there anywhere else more suitable?

Here is fine.

> I've written some Ocaml code to solve the problem of placing 8 queens on a
> board so that none of them attack each-other.  I've also written a C++
> equivalent which is running much more quickly than the Ocaml.

The programs are not equivalent. Profiling shows the C++ is calling 
is_threatened 1,915 to get 1 solution whereas the OCaml is calling it 129,030 
times.

So the algorithms are different.

The code is also needlessly verbose and inefficient. There's no point in 
declaring sum types with one contructor:

type posn = Posn of int * int;;
type board = Board of square array * int;;

There is no need for pattern matches with only one pattern:

let is_threatened queen square =
  match queen, square with
    (x1, y1), (x2, y2) ->
      x1 == x2 ||
        y1 == y2 ||
        (x2 - x1) == (y2 - y1) ||
        (x1 - y2) == (x2 - y1);;

let is_threatened (x1, y1), (x2, y2) =
  x1 == x2 || y1 == y2 || x2 - x1 == y2 - y1 || x1 - y2 == x2 - y1;;

The program is also convoluted, e.g. add_queen converts the same value to and 
from index<->coords.

I'm not sure it is even worth having a board data structure. Why not have an 
implicit board and a list of queen positions? I'll have a go...

> ocamlopt -noassert -unsafe -ccopt -O3 -ccopt -fomit-frame-pointer q.ml -o
> q.exe

Don't bother with the optimisation switches. Just use:

  ocamlopt q.ml -o q

You don't have any assertions, bounds checking is insignificant and you're not 
compiling any C code...

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
Objective CAML for Scientists
http://www.ffconsultancy.com/products/ocaml_for_scientists


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

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

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <15946.213.30.139.86.1167315231.squirrel@webmail.nerim.net>
2006-12-28 16:03 ` [Caml-list] Question on writing efficient Ocaml Ian Oversby
2006-12-28 17:00   ` Richard Jones
2006-12-28 22:23   ` Jon Harrop
2006-12-29  9:42     ` Ian Oversby
2006-12-28 11:42 Ian Oversby
2006-12-28 16:26 ` [Caml-list] " Jon Harrop
2006-12-28 17:13   ` skaller
2006-12-29  6:05     ` Jacques Garrigue
2006-12-29 11:15       ` Mattias Engdegård
2007-01-06  0:52         ` Nathaniel Gray
2007-01-06  1:01           ` Philippe Wang
2007-01-06  1:15           ` brogoff
2007-01-06  2:27             ` Martin Jambon
2007-01-08 22:23               ` Nathaniel Gray
2006-12-29  1:23 ` Andrej Bauer
2006-12-29  9:58   ` Ian Oversby
2006-12-29  2:07 ` Jon Harrop
2007-01-03 16:43   ` Serge Aleynikov

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 NNTP newsgroup(s).