Archived

This forum has been archived. Please start a new discussion on GitHub.

Passing complex data structures: output parameters or return values?

Hello!

I'm puzzled about passing complex data (such as structures, sequences and dictionaries) from the server to clients. Actually, I'd rather prefer to use output parameters because of performance issues (the copy constructors or operators of structures, std::vector and std::map are not called when using a C++ server):
dictionary<string, string> PropertyList;
interface TestServer
{
    void GetProperties(out PropertyList properties);
};
But I was confused with the Ice sources (IcePatch2 namely):
interface FileServer
{
    // ...
    ["ami"] nonmutating FileInfoSeq getFileInfoSeq(int partition)
	throws PartitionOutOfRangeException;
    nonmutating ByteSeqSeq getChecksumSeq();
    nonmutating Ice::ByteSeq getChecksum();
    ["ami"] nonmutating Ice::ByteSeq getFileCompressed(string path, int pos, int num)
	throws FileAccessException; 
    // ...
};

Is there any reason to use such return values in this code? Can you comment which approach is more preferable?

Comments

  • marc
    marc Florida
    I'm afraid I don't understand your comments about the performance differences between return values and out parameters. For return values, a copy constructor is called, while for out parameters an assignment operator (or the logical equivalent) is used. Why do you think one is faster than the other?
  • Instead of
    ByteSeqSeq
    IcePatch2::FileServerI::getChecksumSeq(const Current&) const
    {
        ByteSeqSeq checksums(256);
    
        for(int node0 = 0; node0 < 256; ++node0)
        {
    	checksums[node0] = _tree0.nodes[node0].checksum;
        }
    
        return checksums;
    }
    

    we can use the following method:
    void
    IcePatch2::FileServerI::getChecksumSeq(ByteSeqSeq & checksums, const Current&) const
    {
        checksums.resize(256);
    
        for(int node0 = 0; node0 < 256; ++node0)
        {
    	checksums[node0] = _tree0.nodes[node0].checksum;
        }
    }
     
    

    Is there an assignment operator called in the second case?
  • marc
    marc Florida
    Ok, I see what you mean. I thought you were talking about Ice internals. You are right, for this specific case, you would save one call to a copy constructor if you would use an out parameter instead of a return value. However, this is not really something specific to Ice, but applies to C++ in general. For example, if you use the Java mapping, there is no difference.

    For this specific case, I believe that a clean interface is more important than a performance gain that might hardly be measurable.
  • In the CORBA C++ mapping, the cost of the copy is avoided: instead returning by value, the CORBA C++ mapping returns a pointer to a heap-allocated value and makes the caller responsible for deleting the return value. Of course, that makes a complete mess and opens up the mapping to memory leaks all over the place. The Ice mapping avoids this mistake: any use of the APIs that compiles is guaranteed not to leak or corrupt memory. (People who have used the CORBA C++ mapping will tell you in no uncertain terms how important that is...)

    In general, I very much doubt that you would notice the cost of the additional copy, especially when considering that the call typically will go remote and incur the latency of a remote call: the cost of going on the wire will be far greater than the cost of the copy. So, as a rule, I would avoid using out-parameters for the sake of efficiency. Instead, I'd look at the usability of the resulting API. Quite often, return values are more convenient than out-parameters. For example, when you want to string calls together:
    p1->setValue(p2->getValue())
    
    As opposed to:
    ValueT v;
    p2->getValue(v);
    p1->setValue(v);
    

    So, the decision between a return value and an out-parameter is really one of API style rather than efficiency. And, as Marc pointed out, your concern applies to C++, but does not apply to Java or C#. Seeing that Slice is language-independent, your Slice design should be driven by the abstract, language-independent considerations of the API, not by the mapping for a particular language.

    Cheers,

    Michi.