Boost logo

Boost :

Subject: Re: [boost] Interest in Remote Procedure Call Library?
From: Daniel Larimer (dlarimer_at_[hidden])
Date: 2010-02-10 22:06:24


I really think that benchmarking is pre-mature at this point. Initial "benchmarks" were only to demonstrate that most of the work is done at compile time and the result is a near "optimal" light-weight call into your stub's delegate code which is responsible for "invoking the RPC method".

In the example below s.add(1,2) was an inline call to:

        inline typename traits::result_type operator() (PARAM_ARGS)
        {
            return ((T*)( method_impl_base::d->self)->*m_ptr)(PARAM_NAMES);
        }

Where T == MyClass
method_impl_base::d == pointer to delegate
self == &c
m_ptr == &MyClass::add

struct delegate<T> {
        T* self;
        struct method_impl_base
        {
                delegate* d;
        }
};

 
Suppose you wanted to provide a backend that invoked (synchronously) over a socket, you would define a new delegate type that provided the types for each method and the function above would become (something like)

         inline typename traits::result_type operator() (PARAM_ARGS)
        {
                 method_impl_base::d->socket << method_id << params;
                traits::result_type result;
                method_impl_base::d->socket >> result;
                return result;
        }

Now clearly you would want to change the return type to future<result_type> and make the whole exchange asynchronous and perhaps even give the user some control over the quality of service, priority, acks, ordering, etc. The point is that the delegate class can define any and all of those kinds of details while the writer of MyClass merely had to specify their interface in terms of a generic "delegate template" that can be defined later.

In my case, I have defined a TCP/UDP RPC protocol and hooked it up to a delegate. None of the other developers need to deal with packing/unpacking or modifying their code for new protocols. As far as they are concerned they write a class, list the class name, base classes, method names, and signals in a macro and it can be exposed to the network and easily accessed remotely through the stub/proxy object.

Compare this to the alternatives: write an interface description in some kind of IDL, hook it into your build process to generate the code, ... and after all of that work, if you want to change from dbus to corba to shared memory or subspace you need to start all over again.

The key to making this truly portable is to define the delegate concept clearly in terms of both synchronous and asynchronous operations and abstract ideas regarding delivery characteristics (guaranteed, ordered, sync, async, priority )

I have a good first stab at this and a working proof of concept as well as several earlier revisions based upon boost:;any and dynamic runtime type system. The dynamic system works, but suffers from the following drawbacks:

        1) Requires registering of data types in a factory
        2) Limited compile time checking of your RPC
        3) Lots of dynamic memory allocation for dealing with dynamic types
        4) Not really practical for embedded systems

How does this relate to boost? Well I figured that such a library would be sufficiently general enough and hopefully elegant enough that it would fit in next to all of the other great libraries. Plus, I am tired of writing my own code (ptree w/ json, meta unit conversion, signals, reflection system), only to have some boost library come out that was substantially similar to my own code.

Regardless, I welcome any collaboration on this subject from this community. My one concern is that this thread of discussion is may not be appropriate for this forum. If there is a better place to hash out what would make the ideal RPC / IDL library for boost let me know.
        

Some of the challenges I face in designing this library include:

Given the following:

struct stub<MyClass, delegate>
{
        delegate* d;

        struct method1 : public delegate::method<signature> { } m1;
        struct method2 : public delegate::method<signature> { } m2;

         std::vector< delegate::method*> methods;
};

What is the best way to get methods automatically populated with pointers to m1 and m2. Hand coding the constructor to stub<> would either require some MACRO MAGIC or the developer to list every method twice. Additionally m1 and m2 need to be initialized with a pointer to the delegate or even just to stub<MyClass,delegate> so that they have the necessary context to carry out their call.

I have managed to accomplish it using some "temporary global" variables (thread safe), and some clever use of constructor/destructors in base classes. I am curious if anyone else has ever had such a problem or knew of a good way to solve it. I would also be curious if boost::fusion could perhaps make it possible to iterate over the types in stub?

Ultimate goal is the following macro which is the minimum information the user must provide to define their interface so that it can be used with any delegate.

DEFINE_STUB( CLASS, INHERITS( BASE1, BASE2, ... )
        METHOD(add)
        METHOD(sub, int(double,double), float(int,int)) // overloaded
)

If there were some way to automatically deduce more information then all the better! Leveraging something like Boost.Mirror would actually require more input from the user. A potential option if they are using Boost.Mirror for other things, but not ideal of they only want to do RPC.

Dan

On Feb 10, 2010, at 8:00 PM, OvermindDL1 wrote:

> On Wed, Feb 10, 2010 at 4:36 PM, Daniel Larimer <dlarimer_at_[hidden]> wrote:
>> I cannot post the code at this time (still pending employer approval in spite of the fact that 100% of the development of this code refractor is unpaid!)
>>
>> Ok, benchmark running on Mac OS X with g++ -O2
>>
>> MyClass c;
>> MyClass* cp = &c;
>>
>> stub<MyClass> s(cp);
>>
>> int (MyClass::*a)(int,int) = &MyClass::add;
>>
>> comparing (cp->*a)(1,2) to s.add(1,2)
>>
>> On average there is less than .001 us per call difference between the two methods for a trial run of 1024^3 invocations.
>>
>> Of course the compiler could optimize c.add(1,2) to the point that it was "unmeasurable".
>>
>> It is as I would have expected, the entire abstraction layer has almost no run time overhead and so the rest of the performance will be in how fast you can serialize your data and send it out the port.
>
> Any chance of just compiling it into a standalone exe/dll for win32
> then so I can test it on the same platform as mine?
> _______________________________________________
> Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk