Version française
Home     About     Download     Resources     Contact us    
Browse thread
[Caml-list] Object-oriented access bottleneck
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Nuutti Kotivuori <naked+caml@n...>
Subject: Re: [Caml-list] Object-oriented access bottleneck
Jacques Garrigue wrote:
> First to Brian Hurt:

[...virtual calls...]

> This is pretty optimal in terms of performance.

When I read parts of the implementation of class system in OCaml, I
must say that I was pretty taken by the implementation of late binding
calls. It is not much slower than the C++ equivalent in the simple
case, it can actually be better in heavily inherited class compared to
C++ and it implements so simply the ability to call method "foo" from
any class anywhere that implements a method by that name.

> Next, to Nuutti Kotivuori:
>
>> Ah, right! So it would be possible to inline the "c"'s method "m"
>> into the body of method "p" in class "a" - just that the compiler
>> doesn't do that?
>
> Yes. But not as easy as it sounds, as the current compilation scheme
> for classes "hides" the actual code for methods deep inside the
> class creation function.  In theory, this should be possible if
> there is a real incentive to do that. Yet, to be really useful, this
> would require having final methods too, since having to go through
> super is a bit far fetched.

Right. Well, at the moment I am more interested in the possibility to
do that than if it is cost-effective to do that now.

>> This is mostly an academic problem for me right now, as I'm just
>> getting into OCaml. So no actual production code is at hand.
>
> That's where we are a bit in a chicken and egg problem: if there is
> no program, there is nothing to optimize, but if it is not
> optimized, nobody will write performance critical code to start
> with...  (Some people have sent me pointers to code I should look
> at)

Yeah - that's the way with a lot of languages that have not yet
reached quite mainstream usage. Though in this particular case, the
question is more of whether the language design allows those
optimizations to be implemented, when they are needed, or if changing
the language itself is needed then.


[...]

> Unrelated points: why is "id" mutable? and you probably don't need
> the unit argument to get_id.

Oh, just crud I came up with when coming up with the example. I was
going for a bit more extensive one, but then trimmed it down.

> Now, what can we hope to optimize/inline.
> Here I would say: nothing. Due to ocaml's object model, calls to an
> unknown object's method must go through the vtable, as the object
> could be from any class sharing the same interface.
>
> The only things that final methods would buy you would be:
> method final get_id () = id
> method compare (x:'a) =
> let x_id = x#get_id () and my_id = self#get_id () in
> if my_id < x_id then -1 else if my_id > x_id then 1 else 0
> Here, we should be able to inline self#get_id.
> Also,
> let a = new atom in
> a#get_id ()
> Here we know the actual class of a, so a#get_id could (in
> theory) be inlined.
>
> As you can see, this is pretty limited.

Yeah, indeed it is - and I see no need to change that behaviour. But,
if we could somehow say that every object _has_ to be exactly of type
atom, and not just adhering to the interface of atom (like we know the
case to be in your second example there), we could inline the whole
thing.

Ofcourse this isn't so useful if it doesn't work with inheritance -
since why would we be using objects if they are all going to be the
same type anyway - and making it work with inheritance again is not
possible without breaking the model somehow.

>> It's good to know that progress is being made on the object front,
>> especially in terms of performance.
>
> Not so much in terms of performance. The concern is more to get
> smaller code, and to allow marshalling (in some cases). And of
> course, not to degrade performance.

Well, it's still in the same ballpark.

>> then f will accept any object that has exactly the interface of a.
>
> This is the ocaml object model. "a" in types is not a class, just an
> interface. Hard to change without obfuscating the language.

Right. I don't know enough of things to really know how hard it would
be to have classes also be a strict type and to be able to require
that - or how much new syntax it would require.

>> - no way to have a method/function inside a class that could be
>> used by several methods, would have access to class variables and
>> would be inlined. It would not need to be visible to the
>> outside. Like mentioned, this can be circumvented for non-mutable
>> variables by just giving them as parameters.
>
> This one is theoretically possible.

Well, if method calls inside the class are cheap enough, or if they
can be inlined, this won't be needed.

> What's wrong with your definition of (-->) ? It already works, isn't
> it?  And in native code it should be inlined.  I would prefer not to
> touch syntax at all.

Well, nothing as such - it already works. I guess it's remnants of C++
where I'd expect to be able to use objects for this sort of thing, and
have all the calls inlined all the way.

I did some naive tests on performance, and this is what I came up
with:

,----[ class.ml ]
| class atom =
| object
|   val mutable a = 0
|   method set_a x = a <- x
|   method get_a () = a
| end
| 
| let c = new atom;;
| 
| for i = 0 to 1000000000 do
|   c#set_a 3
| done;;
| 
| print_endline ("A: " ^ (string_of_int (c#get_a ())));;
`----

,----[ classfield.ml ]
| class atom =
| object
|   val mutable a = 0
|   method set_a x = a <- x
|   method get_a () = a
| end
| 
| let c = new atom;;
| 
| let rc = Obj.repr c;;
| let r3 = Obj.repr 3;;
| 
| for i = 0 to 1000000000 do
|   Obj.set_field rc 2 r3
| done;;
| 
| print_endline ("A: " ^ (string_of_int (c#get_a ())));;
`----

,----[ classrec.ml ]
| class atom =
| object
|   val mutable a = 0
|   method set_a x = a <- x
|   method get_a () = a
| end
| 
| type nr = {
|   mutable vtable : int;
|   mutable id : int;
|   mutable a : int;
| }
| 
| let c = new atom;;
| 
| let rc = Obj.magic c;;
| 
| for i = 0 to 1000000000 do
|   rc.a <- 3
| done;;
| 
| print_endline ("A: " ^ (string_of_int (c#get_a ())));;
`----

,----[ record.ml ]
| type atom = {
|   mutable a : int
| }
| 
| let set_a (a:atom) x = a.a <- x;;
| let get_a (a:atom) = a.a;;
| 
| let c = { a = 0 };;
| 
| for i = 0 to 1000000000 do
|   set_a c 3
| done;;
| 
| print_endline ("A: " ^ (string_of_int (get_a c)));;
`----

And the timing values I got over the different implementations:

,----[ class ]
| real    0m23.322s
| user    0m22.760s
| sys     0m0.110s
`----

,----[ classfield ]
| real    0m25.685s
| user    0m25.150s
| sys     0m0.100s
`----

,----[ classrec ]
| real    0m4.433s
| user    0m4.330s
| sys     0m0.010s
`----

,----[ record ]
| real    0m5.540s
| user    0m5.510s
| sys     0m0.030s
`----

So it seems pretty clear that the two last ones were able to inline
the calls, and the two first ones weren't. So, if I can be 100%
certain the class given in is of the exact type I'm expecting it to
be, I can just magically make it a record and access the fields
directly, and get the performance improvements.

Anyhow, thanks for your time - I'm probably trying to investigate more
and learn more of OCaml before going further into this - as many of my
comments are probably just due to inexperience in OCaml.

-- Naked

-------------------
To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr
Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners