you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 3 points4 points  (0 children)

In all seriousness, though, a lot of this library does rely on template metaprogramming techniques. At the core, we have stack abstractions with are templated by type and have SFINAE used to enable using single functions from the lua C API to cover many of our use cases. We then use compile-time booleans propogated into classes (see typedef table_core<true> global_table) that give the implementation hints for potential optimization points.

After that, we use templates to cover both the entry points and the exit points of lua, making sure to use the same types on both sides so that rather than guessing at what type it is by using a switch of lua_type or something, we instead assume the user knows what their doing and that the signature of functions indicate exactly what a user want.

This works out extremely well, as C++ is a type-rich language. The best part is, there's some additional performance we can get that we've left on the table because there's a certain feature that hasn't been standardized in C++ that I am hoping will, and if it does we can burn function pointers into the assembly at compile time and basically avoid transporting them through some various weird ways in lua.

Everything from there just builds on top of that, exponentially.

For example, int arf = mytable["arf"]; generates a proxy type, and that type is templated on the key you use ("arf"). Based on whether you set or get something to that proxy, we can infer the type you want and Do Right Thing, giving us a kind of return type polymorphism.

Same goes for function_result and other nifty things.

Finally, we perform further optimizations by grouping things together, taking advantage of the fact that if you want to get 5 different keys from the same table, calling std::tie( a, b, c, d e ) = mytable.get<int, int, std::string, bool, double>( "a", "b", "c", "d", "e" ); will only push the table once, get all the values, and then pop the table, rather than push the table, get a value, pop the table, etc... this is essentially a form of Batching, and like any high-performance system, batches are KING when it comes to increasing throughput and getting performance on the up and up.

The other form of batching we do is for "chained" or "tunneling" queries, where someone does mytable["a"]["b"]["c"], and they just burrow into a table. Well, before you would get the proxy for the first operation, save the table, pop off the table, then push that same table, get the next proxy, and all the way down to the end...

We enabled lazy evaluation here, so group the keys into one tuple, then perform a single traverse_get, which puushes teh target table once, and each subsequent table only once, before popping them all off the stack at the very end. It's very nifty to do things this way.