RTL: Relational Template Library

Hi All. I just uploaded the latest version of Dmitriy Arapov's and my Relational Template Library (RTL) to the Boost file vault, http://tinyurl.com/8lbmg. This version is compatible with Boost 1.33. The RTL was discussed on this list at some time in the past. The early version of RTL is described in the March 2004 issue of CUJ. The main difference from currently discussed RML is that we decided against using SQL - like interface, and use the relational algebra-based interface instead. This choice is caused by our belief that the optimizer -- the module converting SQL query into an optimized relational expression -- is one of the most sophisticated parts of modern database engines. Also, writing a good optimizer in a system like RTL (or RML) would have to be done in large part at compile time, by the means of template meta-programming. This task doesn't seems to be feasible, at least currently. OTOH, writing a primitive optimizer, would, in our opinion do more harm than good. So, the RTL gives its user the possibility to build arbitrary relational expressions by hand. We augment the pure relational algebra with a few things, such as sort order, indexing, groupby, join, etc. We also allow indexing of any relational expressions, not just tables. So, for example, a user can do a selection on some arbitrary complicated criteria, and then index this selection. This would ultimately result in the index storing just keys to these records. This index would be incrementally updated when the underlying table has been changed. Achieving the same effect with conventional approach would require creation of a much larger index on a calculated field. Another example -- you have table of cities, and you need to get all the pairs, such as the distance between them is less than certain number. How would one solve the problem with the conventional SQL approach? This is a self-join. But how to make this query fast? With RTL approach, we could create an index on this self-join. The RTL can currently be compiled with VC7.1 or GCC 3.3+ (although with GCC there might still remain some naming conflicts between MPL and STL, that show up with certain usages) Regards, Arkadiy

On Wed, Oct 05, 2005 at 09:17:18AM -0400, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
The RTL was discussed on this list at some time in the past. The early version of RTL is described in the March 2004 issue of CUJ. The main difference from currently discussed RML is that we decided against using SQL - like interface, and use the relational algebra-based interface instead.
The most important part for a library like that, is the ability to have the table stored in a file, and only partially mapped into memory. So some tree datastructure like a B+tree is an important requirement for a database library. Any chance that this will happen soon? Maybe you remember that I started an attempt to write such a datastructure, but I lost myself in details and had to give up, as soon as exams took most part of my free time. From the interface point of view, I also prefer the relational algebra "syntax". It somehow looks more familiar to me.
The RTL can currently be compiled with VC7.1 or GCC 3.3+ (although with GCC there might still remain some naming conflicts between MPL and STL, that show up with certain usages)
Because gcc also considers structures when doing adl of a function call? In some previous, and also private discussions you mentioned that you would like to have RTL rewritten, because you made some tradeoffs to support older versions of gcc and vc. Is that versions the overall rewrite? Regards Andreas Pokorny

"Andreas Pokorny" <andreas.pokorny@gmx.de> wrote
On Wed, Oct 05, 2005 at 09:17:18AM -0400, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
The RTL was discussed on this list at some time in the past. The early version of RTL is described in the March 2004 issue of CUJ. The main difference from currently discussed RML is that we decided against using SQL - like interface, and use the relational algebra-based interface instead.
The most important part for a library like that, is the ability to have the table stored in a file, and only partially mapped into memory. So
It's important, but I wouldn't say "most important". For many tasks having the ability to serialize tables should be enough.
some tree datastructure like a B+tree is an important requirement for a database library. Any chance that this will happen soon? Maybe you remember that I started an attempt to write such a datastructure, but I lost myself in details and had to give up, as soon as exams took most part of my free time.
So, "Any chance that this will happen soon?" :)
From the interface point of view, I also prefer the relational algebra "syntax". It somehow looks more familiar to me.
The RTL can currently be compiled with VC7.1 or GCC 3.3+ (although with GCC there might still remain some naming conflicts between MPL and STL, that show up with certain usages)
Because gcc also considers structures when doing adl of a function call?
Yes.
In some previous, and also private discussions you mentioned that you would like to have RTL rewritten, because you made some tradeoffs to support older versions of gcc and vc. Is that versions the overall rewrite?
No, this is just the port to Boost 1.33 (vary little changes, in fact). I would not think it should be fully rewritten, though, just re-factored to simplify the implementation. A few design choices, made in the very beginning, mostly related to the lack of partial template specialization in VC6... Nevertheless I wanted to once more attract people's attention to the library because the interest to relational issues seems to pop-up, and again, I don't believe to mimick SQL is the right way to go. The SQL was designed as yet another attempt to try to make computers understand a language that looks more or less like plain English. Now I don't think that even database comunity considers it a way to proceed. One of the main things that prevented us from moving forward, was not enough interest, but now we can re-consider this -- the number of downloads seems pretty high :) Regards, Arkadiy

On 10/5/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Andreas Pokorny" <andreas.pokorny@gmx.de> wrote
On Wed, Oct 05, 2005 at 09:17:18AM -0400, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
The most important part for a library like that, is the ability to have the table stored in a file, and only partially mapped into memory. So
It's important, but I wouldn't say "most important". For many tasks having the ability to serialize tables should be enough.
Serialize tables is nice and many memory-based apps can benefit but for most databases persistence is a must. I agree with previous statement that persistence is the top priority. RML has tackled this with a separate persist library that provides the persistent STL containers. Also shmem I think provides persistent stl containers using mmap files (persist library even has benchmarks comparing to mysql)

On 10/05/2005 01:34 PM, Jose wrote: [snip]
Serialize tables is nice and many memory-based apps can benefit but for most databases persistence is a must.
I agree with previous statement that persistence is the top priority. RML has tackled this with a separate persist library that provides the persistent STL containers.
Why can't the existing boost serialize library be used instead of a separate persist library?

On 10/5/05, Larry Evans <cppljevans@cox-internet.com> wrote:
Why can't the existing boost serialize library be used instead of a separate persist library?
I am not very familiar with the serialize library but I don't think they tackle persistence in the sense of providing persistant STL containers, i.e. containers that you can use in your app but that are stored in a file rather than the well known memory STL containers

On Wed, 5 Oct 2005, Jose wrote:
On 10/5/05, Larry Evans <cppljevans@cox-internet.com> wrote:
Why can't the existing boost serialize library be used instead of a separate persist library?
I am not very familiar with the serialize library but I don't think they tackle persistence in the sense of providing persistant STL containers, i.e. containers that you can use in your app but that are stored in a file rather than the well known memory STL containers
The use of serializability as a prerequisite for storage in a persistent container seems quite logical. Im currently looking if I can wrap a simple string/file based persistent priority queue into a simple template library that is somewhat compatible with std::priority_queue (libppq on sourceforge). This should be quite do-able, but as Im not that experienced with templates, it may take some more efford. I think that dependancy on the serialisation library for classes that can be used to instanciate a persistent container template from would be a sane way of working. That way you just have to create a persistent container of strings using the filesystem, and wrap this class in a template. What I was currently thinking of was to require that each class that could be put into the persistent priority queue template should have its operator =(string) and operator string() overloaded by a serialisation method, so the template could simply cast its internaly used strings to and from the specified class, and each class should have its operator int() overloaded so that its integer priority could be retreived from the objects by using simple casts in the template. One other thing to considder are the aplicability and efficiency of algoritms paterns and adapters on persistent containers. I had to choose a vector of persistent fifo design for my persistent priority queue as a the heap aproach apeared to perform poorly in comparison. I think there may be more paterns that may be efficient in memory, but may be less optimal either in disk usage or in disk IO when mapped to files. I dont believe there is a fifo in stl, only a deque, but the deque again would be hard to code efficiently for both space and speed. Simple memory based templates may be quite a bit harder to implement when using serialisation+FS based storage. I think I could adjust the persistent fifo to implement shrinking on both sides, making it usable for some more algoritms that normaly work with a deque, but pushing data to both sides as can be done with the deque would be very hard to do efficiently. I think my libppq code might be of some use. I could probably use some help with the templates. Random access containers would I think be somewhat harder to implement efficiently. Rob

Serialize tables is nice and many memory-based apps can benefit but for most databases persistence is a must.
I agree with previous statement that persistence is the top
RML has tackled this with a separate persist library that
On 10/05/2005 01:34 PM, Jose wrote: [snip] priority. provides the
persistent STL containers.
Why can't the existing boost serialize library be used instead of a separate persist library?
RML is not a Boost library - it uses standard streams to serialize data. It's as simple as file << table1 << table2 << table3; file >> table1 >> table2 >> table3; The reason for persist is to make loading/saving faster. Calum

Some reference info on the topics discussed here and in the RML thread that might be useful: 2001 USENIX paper: PSTL - The Persistent Standard Template Library for C++ http://www.infosys.tuwien.ac.at/Staff/tom/Publications/Gschwind2001-pstl.pdf http://www.infosys.tuwien.ac.at/NewsCache/pstl.html This papers covers the issues and has simple app benchmarks that compare to the widely used Berkeley DB (alse note Berkeley DB provides the underlying b-tree for MySQL) Reflection support by means of template metaprogramming http://www.di.unipi.it/~attardi/Paper/GCSE01.pdf Which has a good discussion on interfacing to a relational table using meta programming

Some reference info on the topics discussed here and in the RML thread that might be useful: 2001 USENIX paper: PSTL - The Persistent Standard Template Library for C++ http://www.infosys.tuwien.ac.at/Staff/tom/Publications/Gschwin d2001-pstl.pdf http://www.infosys.tuwien.ac.at/NewsCache/pstl.html This papers covers the issues and has simple app benchmarks that compare to the widely used Berkeley DB (alse > note Berkeley DB provides the underlying b-tree for MySQL) Reflection support by means of template > metaprogramming http://www.di.unipi.it/~attardi/Paper/GCSE01.pdf Which has a good discussion on interfacing to a relational table using
meta programming Those are very helpful links. While benchmarks obviously only approximate real-world performance, I would be very interested in comparing the various approaches using a standard task. By providing a solution to a small "real world" problem, it enables you (the Boost developers) to compare both the performance and how a library looks like to use in practice, and is much more preferable to hypothesising. And we can also compare to the "STL" solution to see whether relational templates/queries really do make an application easier to develop. Regards, Calum

"Jose" <jmalv04@gmail.com> wrote
On 10/5/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Andreas Pokorny" <andreas.pokorny@gmx.de> wrote
On Wed, Oct 05, 2005 at 09:17:18AM -0400, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
The most important part for a library like that, is the ability to
have
the table stored in a file, and only partially mapped into memory. So
It's important, but I wouldn't say "most important". For many tasks having the ability to serialize tables should be enough.
Serialize tables is nice and many memory-based apps can benefit but for most databases persistence is a must.
I agree with previous statement that persistence is the top priority. RML has tackled this with a separate persist library that provides the persistent STL containers. Also shmem I think provides persistent stl containers using mmap files (persist library even has benchmarks comparing to mysql)
Well, RTL addresses (or delays) the issue of persistense by separating table implementation from the rest of the library. We currently give the user a choise between an std::vector - based implementation and Boost.Multi-index - based implementation. A disk-based implementation can also be developed. (alternatively, someone may comeup with a persistent std::vector :-) Regards, Arkadiy

On 10/5/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Well, RTL addresses (or delays) the issue of persistense by separating table implementation from the rest of the library. We currently give the user a choise between an std::vector - based implementation and Boost.Multi-index- based implementation. A disk-based implementation can also be developed. (alternatively, someone may comeup with a persistent std::vector :-)
I need to learn more about RTL. I think the combination of RTL + RML is definetely needed as a boost library. At least myself can't wait for that library ! The question is who provides the persistent STL containers, maybe should be the Boost.Multi-Index as this is the higher level container. There is a bit of overlap: RTL --> uses Boost.Multi-Index - no persistent container RML --> own multi-index container and own persistent container library via mmap files shmem --> persistent versions of STL containers like std::map (via mmap files) Boost.Multiindex --> no persistent version I liked RML because is further along the idea of being a replacement for a traditional database. What I think the final library needs is: - combine RTL and RML in a single syntax (as friendly as possible) - use Boost.Multi-Index (assuming it provides a persistent container) or use persist or shmem persistent container

The RTL was discussed on this list at some time in the past. The early version of RTL is described in the March 2004 issue of CUJ. The main difference from currently discussed RML is that we decided against using SQL - like interface, and use the relational algebra-based interface instead.
This choice is caused by our belief that the optimizer -- the module converting SQL query into an optimized relational expression -- is one of the most sophisticated parts of modern database engines. Also, writing a good optimizer in a system like RTL (or RML) would have to be done in large part at compile time, by the means of template meta-programming. This task doesn't seems to be feasible, at least currently.
OTOH, writing a primitive optimizer, would, in our opinion do more harm than good.
By "harm" do you mean have worse performance? If you're going to make claims about the performance of an approach, then you need to have benchmarks. You can't argue about air. For example the RML benchmarks http://visula.org/relational/benchmarks.html show RML to be matching or outperforming std::map. Would you care to benchmark your RTL versus RML or the STL before making such claims? I think that would settle which approach did more "harm". Calum

"Calum Grant" <calum@visula.org> wrote
The RTL was discussed on this list at some time in the past. The early version of RTL is described in the March 2004 issue of CUJ. The main difference from currently discussed RML is that we decided against using SQL - like interface, and use the relational algebra-based interface instead.
This choice is caused by our belief that the optimizer -- the module converting SQL query into an optimized relational expression -- is one of the most sophisticated parts of modern database engines. Also, writing a good optimizer in a system like RTL (or RML) would have to be done in large part at compile time, by the means of template meta-programming. This task doesn't seems to be feasible, at least currently.
OTOH, writing a primitive optimizer, would, in our opinion do more harm than good.
By "harm" do you mean have worse performance?
Yes, and also (possibly) inability to handle more complicated cases.
If you're going to make claims about the performance of an approach, then you need to have benchmarks. You can't argue about air. For example the RML benchmarks
http://visula.org/relational/benchmarks.html
show RML to be matching or outperforming std::map.
Would you care to benchmark your RTL versus RML or the STL before making such claims? I think that would settle which approach did more "harm".
Note that I didn't say RML's approach is harmful. All I said is that a simplistic approach to implementing optimizer is IMO inacceptable. Are you saying your optimizer is simplistic? :-) I believe that talks about performance are premature. I think it's time to talk about what can and what can't be done with this libraries. Then comes the optimization. As for benchmarks, for such simple case as yours, you don't need RTL -- you can benchmark sorted std::vector or Boost.Multi_index. The performance of RTL will not be different, since it's based on these containers. To outperform STL is definitely not one of RTL's tasks. For more complicated cases though, such as ones that I described in my original post, RTL does provide an efficient, index-based solution, while other libraries, AFAIK, don't. Regards, Arkadiy

Arkadiy Vertleyb wrote:
OTOH, writing a primitive optimizer, would, in our opinion do more harm than good.
By "harm" do you mean have worse performance?
Yes, and also (possibly) inability to handle more complicated cases.
Well I certainly agree that a library that couldn't handle complicated cases, or whose performance was poor, would need a rethink. But I don't think RML is in that category. It has excellent performance and can handle very complex queries. I should exercise caution however - I await to see how well your library copes with 1000000 items, does a self-join, adds another 1000000 items and does a second self join. You sound fairly confident that you will beat me! If you like you can come up with an extremely complex query - and we can compare again.
If you're going to make claims about the performance of an approach, then you need to have benchmarks. You can't argue about air. For example the RML benchmarks
http://visula.org/relational/benchmarks.html
show RML to be matching or outperforming std::map.
Would you care to benchmark your RTL versus RML or the STL before making such claims? I think that would settle which approach did more "harm".
Note that I didn't say RML's approach is harmful. All I said is that a simplistic approach to implementing optimizer is IMO inacceptable. Are you saying your optimizer is simplistic? :-)
Of course not!
I believe that talks about performance are premature. I think it's time to talk about what can and what can't be done with this libraries. Then comes the optimization.
I don't think talking about performance is premature. If we are going to provide a practical library, then performance matters. If a user has a choice between using RTL in a real application, but it is 10 times slower than Boost.MultiIndex, then I expect most users would stick with Boost.MultiIndex. People need to know. On the other hand, I believe that RML is a much easier and more efficient (than std:: containers) way to manage collections. But I am biassed :-)
As for benchmarks, for such simple case as yours, you don't need RTL -- you can benchmark sorted std::vector or Boost.Multi_index. The performance of RTL will not be different, since it's based on these containers. To outperform STL is definitely not one of RTL's tasks.
For more complicated cases though, such as ones that I described in my original post, RTL does provide an efficient, index-based solution, while other libraries, AFAIK, don't.
In that case, could you give an example where RTL would be efficient, while "other libraries" would be inefficient. I have a case for you that might not be so efficient in RTL: What about indexing on multiple columns? This is what RML is designed for, otherwise you may as well just use std::map. Although it sounds a little competitive, I think it's pretty instructive to be able to compare different implementations. Regards, Calum

Although it sounds a little competitive, I think it's pretty instructive to be able to compare different implementations.
I agree benchmarks provide a good practical view of the benefits but you are focusing on memory benchmarks, which is ok, but the large difference is normally in benchmarks which involve io. I will try to run both libraries in my x86_64 box and provide some comparision for the first set of benchmarks. I also want to mention that I find the self-join example quite nice. Would this be useful in implementing an r-tree query ?

"Calum Grant" <calum@visula.org> wrote
Arkadiy Vertleyb wrote:
OTOH, writing a primitive optimizer, would, in our opinion do more harm than good.
By "harm" do you mean have worse performance?
Yes, and also (possibly) inability to handle more complicated cases.
Well I certainly agree that a library that couldn't handle complicated cases, or whose performance was poor, would need a rethink.
But I don't think RML is in that category. It has excellent performance and can handle very complex queries. I should exercise caution however - I await to see how well your library copes with 1000000 items, does a self-join, adds another 1000000 items and does a second self join. You sound fairly confident that you will beat me!
I am not confident about this. We didn't spent a lot of time on optimization. But if I wanted to compete with you on this particular example, I would not use the technique I showed -- I wouldn't use transactions, indexing, etc. -- I would just use a merge-join, and stopped after ten results, like you did, and it would take no time. I might beat you or not, but RTL is flexible enough to allow me to build an expression that would evaluate this (or any other) expression efficiently, with the minimal overhead. And since I do it by hand, I don't have to worry about my optimizer not being able to cope with this.
If you like you can come up with an extremely complex query - and we can compare again.
For starters, try to change the expression to something like abs(c1 - c2) == 1, and output (or just walk) _all_ the results. Then repeat this 1000 times. You can go back from 1000000 to 3000 :-)
If you're going to make claims about the performance of an approach, then you need to have benchmarks. You can't argue about air. For example the RML benchmarks
http://visula.org/relational/benchmarks.html
show RML to be matching or outperforming std::map.
Would you care to benchmark your RTL versus RML or the STL before making such claims? I think that would settle which approach did more "harm".
Note that I didn't say RML's approach is harmful. All I said is that a simplistic approach to implementing optimizer is IMO inacceptable. Are you saying your optimizer is simplistic? :-)
Of course not!
I believe that talks about performance are premature. I think it's time to talk about what can and what can't be done with this libraries. Then comes the optimization.
I don't think talking about performance is premature. If we are going to provide a practical library, then performance matters. If a user has a choice between using RTL in a real application, but it is 10 times slower than Boost.MultiIndex, then I expect most users would stick with Boost.MultiIndex. People need to know.
Of course. However I don't believe RTL even targets this niche. Relational approach is much more than just indexing on multiple fields.
On the other hand, I believe that RML is a much easier and more efficient (than std:: containers) way to manage collections. But I am biassed :-)
As for benchmarks, for such simple case as yours, you don't need RTL -- you can benchmark sorted std::vector or Boost.Multi_index. The performance of RTL will not be different, since it's based on these containers. To outperform STL is definitely not one of RTL's tasks.
For more complicated cases though, such as ones that I described in my original post, RTL does provide an efficient, index-based solution, while other libraries, AFAIK, don't.
In that case, could you give an example where RTL would be efficient, while "other libraries" would be inefficient. I have a case for you that might not be so efficient in RTL: What about indexing on multiple columns? This is what RML is designed for, otherwise you may as well just use std::map.
I don't know if we are much less efficient with multiple columns. Again, the matter of fine tuning. And again, relational approach is much more than just indexing on multiple columns. For just indexing on multiple columns one can use Boost.MultiIndex. IMO, relational approach is about providing multiple views into the same data model. And making these views most efficient. The constrained language of relational algebra allows one to achieve this. This is what RTL is about.
Although it sounds a little competitive, I think it's pretty instructive to be able to compare different implementations.
Nothing wrong about being competitive :) Regards, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote
Another example -- you have table of cities, and you need to get all the pairs, such as the distance between them is less than certain number. How would one solve the problem with the conventional SQL approach? This is a self-join. But how to make this query fast?
With RTL approach, we could create an index on this self-join.
Here is a simple example that illustrates this. The attached program does the following: 1) creates table of one column; 2) self-joins the table 3) selects records where columns are equal. This by itself doesn't make any sence, but it closely approximates the task of finding close cities, stars, billiard balls, monsters and soldiers, etc. Here is the text (Boost CVS head was used to be able to use typeof): ///////////////////// #include <table_delta.hpp> #include <utils.hpp> #include <selection_delta.hpp> #include <key_index_delta.hpp> #include <crossproduct_delta.hpp> #include <rename_delta.hpp> #include <boost/lambda/lambda.hpp> #include <boost/typeof/typeof.hpp> #include <expression_registry.hpp> #include <transaction.hpp> #include <cstdlib> using namespace boost; using namespace boost::lambda; BOOST_RTL_DEFINE_COLUMN(int, c1); struct my_info : rel::table_info< mpl::vector<c1>
{};
typedef rel::table<my_info> my_table; typedef my_table::value_type my_tuple; struct a; typedef rel::alias<c1, a> c2; main() { // populate table my_table t; for (int i = 0; i < 3000; ++i) t.insert(my_tuple(i + 1)); // create and print the view BOOST_AUTO(t2, rel::auto_rename<a>(t)); BOOST_AUTO(xp, rel::cross_product(t, t2)); BOOST_AUTO(pred, _1[c1()] == _1[c2()] ); BOOST_AUTO(sel, rel::selection(xp, pred) ); std::cout << "building index" << std::endl; BOOST_AUTO(idx, (rel::key_index<mpl::vector<c1, c2> >(sel)) ); std::cout << "printing index" << std::endl; rel::print(idx); // update table (index doesn't get fully rebuilt) rel::expression_registry exprs; exprs.add(idx); rel::transaction tr; for (i = 0; i < 10; ++i) tr.insert(t, my_tuple(-i)); tr.commit(exprs); // print the view once again std::cout << "printing updated index" << std::endl; rel::print(idx); return 0; } ///////////////// Regards, Arkadiy

Arkadiy Vertleyb wrote
"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote
Another example -- you have table of cities, and you need to get all the pairs, such as the distance between them is less than certain number. How would one solve the problem with the conventional SQL approach? This is a self-join. But how to make this query fast?
With RTL approach, we could create an index on this self-join.
Here is a simple example that illustrates this. The attached program does the following:
1) creates table of one column; 2) self-joins the table 3) selects records where columns are equal.
This by itself doesn't make any sence, but it closely approximates the task of finding close cities, stars, billiard balls, monsters and soldiers, etc.
Here is the equivalent in RML. Forget 3000 rows, what about adding 1000000 rows, then adding another 1000000 to it? What time does the equivalent RTL take? I don't think self-joins come up that often in real life however. Regards, Calum #include <ctime> #include <relational.hpp> using namespace relational; RM_DEFINE_ROW_1(MyRow, unique<int>, x); int main(int argc, char*argv[]) { int total=0; clock_t t0 = clock(); { table<MyRow> my_table; const int N1=1000000, N2=1000000; for(int i=0; i<N1; ++i) my_table.insert(MyRow(i)); std::cout << select( (my_table, my_table), col<0,0>() == col<1,0>() ).size() << std::endl; // Print only 10 results output_results(limit(select( (my_table, my_table), col<0,0>() == col<1,0>() ), 10)); for(int i=1; i<=N2; ++i) my_table.insert(MyRow(-i)); std::cout << select( (my_table, my_table), col<0,0>() == col<1,0>() ).size() << std::endl; } clock_t t1 = clock(); std::cout << "That took just " << (1000*(t1-t0))/CLOCKS_PER_SEC << "ms\n"; return 0; } Here is the output on my 1.7GHz laptop: 1000000 ((0),(0)) ((1),(1)) ((2),(2)) ((3),(3)) ((4),(4)) ((5),(5)) ((6),(6)) ((7),(7)) ((8),(8)) ((9),(9)) Total 10 results 2000000 That took just 1687ms Press any key to continue Arkadiy Vertleyb wrote
"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote
Another example -- you have table of cities, and you need to get all the pairs, such as the distance between them is less than certain number. How would one solve the problem with the conventional SQL approach? This is a self-join. But how to make this query fast?
With RTL approach, we could create an index on this self-join.
Here is a simple example that illustrates this. The attached program does the following:
1) creates table of one column; 2) self-joins the table 3) selects records where columns are equal.
This by itself doesn't make any sence, but it closely approximates the task of finding close cities, stars, billiard balls, monsters and soldiers, etc.
Here is the text (Boost CVS head was used to be able to use typeof):
///////////////////// #include <table_delta.hpp> #include <utils.hpp> #include <selection_delta.hpp> #include <key_index_delta.hpp> #include <crossproduct_delta.hpp> #include <rename_delta.hpp> #include <boost/lambda/lambda.hpp> #include <boost/typeof/typeof.hpp> #include <expression_registry.hpp> #include <transaction.hpp> #include <cstdlib>
using namespace boost; using namespace boost::lambda;
BOOST_RTL_DEFINE_COLUMN(int, c1);
struct my_info : rel::table_info< mpl::vector<c1>
{};
typedef rel::table<my_info> my_table; typedef my_table::value_type my_tuple;
struct a; typedef rel::alias<c1, a> c2;
main() { // populate table
my_table t;
for (int i = 0; i < 3000; ++i) t.insert(my_tuple(i + 1));
// create and print the view
BOOST_AUTO(t2, rel::auto_rename<a>(t));
BOOST_AUTO(xp, rel::cross_product(t, t2));
BOOST_AUTO(pred, _1[c1()] == _1[c2()] );
BOOST_AUTO(sel, rel::selection(xp, pred) );
std::cout << "building index" << std::endl;
BOOST_AUTO(idx, (rel::key_index<mpl::vector<c1, c2> >(sel)) );
std::cout << "printing index" << std::endl;
rel::print(idx);
// update table (index doesn't get fully rebuilt)
rel::expression_registry exprs; exprs.add(idx);
rel::transaction tr;
for (i = 0; i < 10; ++i) tr.insert(t, my_tuple(-i));
tr.commit(exprs);
// print the view once again
std::cout << "printing updated index" << std::endl; rel::print(idx);
return 0; } /////////////////
Regards, Arkadiy
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/bo> ost

"Calum Grant" <calum@visula.org> wrote
Arkadiy Vertleyb wrote
Here is a simple example that illustrates this. The attached program does the following:
1) creates table of one column; 2) self-joins the table 3) selects records where columns are equal.
This by itself doesn't make any sence, but it closely approximates the task of finding close cities, stars, billiard balls, monsters and soldiers, etc.
Here is the equivalent in RML. Forget 3000 rows, what about adding 1000000 rows, then adding another 1000000 to it? What time does the equivalent RTL take?
Calum, I think you are missing my point. The above example was provided to illustrate my two statements: 1) RTL can index any (arbitrary complicated) expression, not just table; 2) RTL can _incrementally_ update any such index when the underlying table has been changed. Did I use the most efficient expression for this particular task? Of course not. I used cross-product where I would use a range (or merge) join otherwise. I did it _intentionally_, because I wanted to provide an analogy of a complicated condition, such condition for which optimization is simply not possible. I am pretty sure your library figured out how to do this query efficiently, without considering every possible combination (it didn't consider 10^12 combinations, did it?). However, what it would do if the condition was more complicated? Like calculating the distance, matching people by certain characteristics, etc.? RTL can build an index on even slow expression, after which subsequent queries become a few orders of magnitude faster. And the indexes are not broken when the tables are changed. Having said all this, RTL doesn't enforce this kind of usage. If the query needs to be executed only once, or if one doesn't want to consum memory for indexes, any appropriate relational expression can be built, with any number of indexes in different nodes, to achieve needed tradeof between speed, initial response, memory consumption, etc. My firm belief is that this kind of flexibility is simply impossible with SQL-like approach.
I don't think self-joins come up that often in real life however.
I think they are. I just gave examples where they could be (and are) used. This, however, is not about a self-join. This is about any query which is slightly more complicated than just a simple index-based selection. Once again, I don't believe this is a right time for any performance contests. They will not prove anything. I think RML will be faster in some cases, and RTL -- in the others. And a couple of days spent on optimization can dramatically change the picture. What I would like to be discussing instead is the _design principles_ behind the libraries. Regards, Arkadiy

Here is the equivalent in RML. Forget 3000 rows, what about adding 1000000 rows, then adding another 1000000 to it? What time does the equivalent RTL take?
Calum, I think you are missing my point. The above example was provided to illustrate my two statements:
1) RTL can index any (arbitrary complicated) expression, not just table;
In SQL/RML you can index any arbitrarily complicated expression. If you need to index something complex, you create a column for it. For example if you had a table of people, with (name, height, weight). If you needed to work with bmi = weight/(height^2), you create another column for it. people.insert(Person(name, height, weight, weight/(height*height)); The RTL approach would not need to add the fourth column, and that is an advantage that RTL has over RML. Similarly for self-join. If you wanted to store an all-pairs distance between two cities, you create a table (city1, city2, distance). But in practice, in SQL I have never seen an index on anything other than a column. Occasionally I have seen a multi-column index. So this is why supporting computed indexes has not been a priority for RML.
2) RTL can _incrementally_ update any such index when the underlying table has been changed.
Same in RML. You add data, indexes change automatically. You change a field in a row, indexes update automatically. Probably more efficiently.
Did I use the most efficient expression for this particular task? Of course not. I used cross-product where I would use a range (or merge)
join otherwise. I did it _intentionally_, because I wanted to provide
an analogy of a complicated condition, such condition for which optimization is simply not possible.
I am pretty sure your library figured out how to do this query efficiently, without considering every possible combination (it didn't
consider 10^12 combinations, did it?). However, what it would do if the condition was more complicated? Like calculating the distance, matching people by certain characteristics, etc.? RTL can build an index on even slow expression, after which subsequent queries become a
few orders of magnitude faster. And the indexes are not broken when the tables are changed.
That is a nice idea. One could generalize - have a generic observer on any container. It could turn your entire application data-driven. Nice. I have seen observer-driven architectures work extremely well in practice.
Having said all this, RTL doesn't enforce this kind of usage. If the query needs to be executed only once, or if one doesn't want to consum
memory for indexes, any appropriate relational expression can be built, with any number of indexes in different nodes, to achieve needed tradeof between speed, initial response, memory consumption, etc. My firm belief is that this kind of flexibility is simply impossible with SQL-like approach.
I wouldn't knock the SQL approach - it is an extremely effective paradigm for data query and represention. There are some things that would not be possible in SQL, but one can always work around them. For example maintaining an all-pairs distance between two cities. One can simply declare a table (city1, city2, distance), and index on the columns you wanted. Whereas in RTL this data would be stored implicitly in an index, RML would require an explicit table. The reason RML uses the SQL paradigm is because I know that it works. The RTL view approach would make this much easier to maintain. The question is, what are the overheads of this convenience?
I don't think self-joins come up that often in real life however.
I think they are. I just gave examples where they could be (and are) used. This, however, is not about a self-join. This is about any query
which is slightly more complicated than just a simple index-based selection.
Self-joins don't come up in SQL. The assumption you make is that the query analyser is some kind of black box that will fail in mysterious ways. Actually the rules for query optimization are very well spelt out in the tutorial. It is precisely because the rules for indexing are so straightforward that there will be no unexpected performance penalties in RML. It will pick indexes in any complex multi-table conjunction for any comparators.
Once again, I don't believe this is a right time for any performance contests. They will not prove anything. I think RML will be faster in some cases, and RTL -- in the others. And a couple of days spent on
optimization can dramatically change the picture.
I think you should work on that, and then we can see what's what. The only reason I make a fuss is because you say your approach is more efficient.
What I would like to be discussing instead is the _design principles_ behind the libraries.
Okay, that is a good way forward. RTL has (from what I understand) an observer-driven architecture. Correct me if I am wrong. This means that you don't index the underlying data, you index views of the data. RML on the other hand is a multi-index architecture. The query analyser is basically a way of avoiding writing complex index probes, iteration and filters. From an efficiency point of view, it is exactly the same as writing out C++ to manipulate a multi-index. So, RML has the potential to be more efficient because multi-indexes are more efficient than multiple single-indexes. Best Regards, Calum

"Calum Grant" <calum@visula.org> wrote
In SQL/RML you can index any arbitrarily complicated expression.
If you need to index something complex, you create a column for it. For example if you had a table of people, with (name, height, weight). If you needed to work with bmi = weight/(height^2), you create another column for it.
people.insert(Person(name, height, weight, weight/(height*height));
The RTL approach would not need to add the fourth column, and that is an advantage that RTL has over RML.
Similarly for self-join. If you wanted to store an all-pairs distance between two cities, you create a table (city1, city2, distance).
Right, so that your user has to maintain this "index" by hand. And if you have many such queries, your user has to manually maintain all these "indexes". May become a major inconvenience. RTL can do better than that.
But in practice, in SQL I have never seen an index on anything other than a column. Occasionally I have seen a multi-column index. So this is why supporting computed indexes has not been a priority for RML.
The priority of RTL has been to provide maximum flexibility for _any_ kind of query, no matter how complex. With simple queries it's hard to explain why a relational engine should be used rather than, say, Boost.MultiIndex.
2) RTL can _incrementally_ update any such index when the underlying table has been changed.
Same in RML. You add data, indexes change automatically. You change a field in a row, indexes update automatically. Probably more efficiently.
Yes, but RML indexes tables, and this is the same as Boost.MultiIndex does. RTL allows to indexes _any_ expression, which results in completely new capabilities. I don't consider this, BTW, a weekness of RML -- I consider this a major strength of RTL. I don't know of any other library or RDBMS that provides the same.
Did I use the most efficient expression for this particular task? Of course not. I used cross-product where I would use a range (or merge)
join otherwise. I did it _intentionally_, because I wanted to provide
an analogy of a complicated condition, such condition for which optimization is simply not possible.
I am pretty sure your library figured out how to do this query efficiently, without considering every possible combination (it didn't
consider 10^12 combinations, did it?). However, what it would do if the condition was more complicated? Like calculating the distance, matching people by certain characteristics, etc.? RTL can build an index on even slow expression, after which subsequent queries become a
few orders of magnitude faster. And the indexes are not broken when the tables are changed.
That is a nice idea. One could generalize - have a generic observer on any container. It could turn your entire application data-driven. Nice. I have seen observer-driven architectures work extremely well in practice.
We don't use observer -- we think it's too expensive. We utilize our transaction mechanism instead. We store all the modifications inside the transaction object, and, when transaction is committed, re-calculate every index involved.
Having said all this, RTL doesn't enforce this kind of usage. If the query needs to be executed only once, or if one doesn't want to consum
memory for indexes, any appropriate relational expression can be built, with any number of indexes in different nodes, to achieve needed tradeof between speed, initial response, memory consumption, etc. My firm belief is that this kind of flexibility is simply impossible with SQL-like approach.
I wouldn't knock the SQL approach - it is an extremely effective paradigm for data query and represention. There are some things that would not be possible in SQL, but one can always work around them. For example maintaining an all-pairs distance between two cities. One can simply declare a table (city1, city2, distance), and index on the columns you wanted. Whereas in RTL this data would be stored implicitly in an index, RML would require an explicit table.
IMO, SQL is so successfull because of the client-server architecture. Any scripting language would do -- SQL just happend to win the market. AFAIK, the database comunity is not really considering SQL the way to proceed.
The reason RML uses the SQL paradigm is because I know that it works.
The RTL view approach would make this much easier to maintain. The question is, what are the overheads of this convenience?
I don't think self-joins come up that often in real life however.
I think they are. I just gave examples where they could be (and are) used. This, however, is not about a self-join. This is about any query
which is slightly more complicated than just a simple index-based selection.
Self-joins don't come up in SQL.
Why? SELECT e.name as employee, m.name AS manager FROM employees e, employees m WHERE e.manager = m.code (if I remember the syntax correctly)
The assumption you make is that the query analyser is some kind of black box that will fail in mysterious ways. Actually the rules for query optimization are very well spelt out in the tutorial. It is precisely because the rules for indexing are so straightforward that there will be no unexpected performance penalties in RML.
It will pick indexes in any complex multi-table conjunction for any comparators.
Once again, I don't believe this is a right time for any performance contests. They will not prove anything. I think RML will be faster in some cases, and RTL -- in the others. And a couple of days spent on
optimization can dramatically change the picture.
I think you should work on that, and then we can see what's what. The only reason I make a fuss is because you say your approach is more efficient.
Well, as follows from my other post, I am not so sure RTL is less efficient than RML, even right now ;-)
What I would like to be discussing instead is the _design principles_ behind the libraries.
Okay, that is a good way forward.
RTL has (from what I understand) an observer-driven architecture. Correct me if I am wrong.
No, as I already mentioned, we don't use observers -- we think it's too expensive. We let the user specify explicitly what needs to be maintained. Otherwise, we could end up maintaining what's not required.
This means that you don't index the underlying data, you index views of the data.
Correct. Data has one index, maintained by the underlying implementation, currently sorted std::vector or Boost.MultiIndex (we use Boost.MultiIndex not because of its multi-index capabilities, but rather because it allows range queries on partial predicate, which std::set doesn't. All the other indexes are applied and maintained externally. They can be built on top of any relations, including tables. The reason that such indexing is possible is that any relation in RTL allows for range queries (equal_range, lower_bound, upper_bound), that are expressed through the range queries of this relation's parameters. Also RTL garantees uniqueness of the key for any relation. This allows for indexing, where index stores such keys.
RML on the other hand is a multi-index architecture. The query analyser is basically a way of avoiding writing complex index probes, iteration and filters. From an efficiency point of view, it is exactly the same as writing out C++ to manipulate a multi-index.
So, RML has the potential to be more efficient because multi-indexes are more efficient than multiple single-indexes.
Maybe on simple queries, but I am still not sure. OTOH, RTL has flexibility which allows to build complex views, maintain them automatically, and execute them with maximum efficiency. Regards, Arkadiy

Hi, I am trying to compare RML vs RTL performance on a 3GHz x86_64 . The result I got for RML is 1260 ms (see below). The result greatly varies with the compiler optimization. Not sure what was Calum's 1.7GHz environment (his result was 1687ms) without optim. --> 21230 ms g++ -O2 --> 3200 ms g++ -O3 --> 1260 ms (see below) Arkadiy, I would need your latest realease so that I can compile it against boost head (I d/l your library from the vault, pls see compile error below. I also made some changes to compile in Linux) Regards, Jose =========================================== RML 1000000 ((0),(0)) ((1),(1)) ((2),(2)) ((3),(3)) ((4),(4)) ((5),(5)) ((6),(6)) ((7),(7)) ((8),(8)) ((9),(9)) Total 10 results 2000000 That took just 1260ms =========================================== g++ -O6 arkadiy.cpp -o test2 -Irtl -I/usr/local/include/boost-1_33 -L/usr/local/lib rtl/identifiable.hpp: In member function 'unsigned int rel::identifiable::id() const': rtl/identifiable.hpp:22: error: cast from 'char*' to 'unsigned int' loses precision make: *** [test2] Error 1 ===========================================

"Jose" <jmalv04@gmail.com> wrote
===========================================
g++ -O6 arkadiy.cpp -o test2 -Irtl -I/usr/local/include/boost-1_33 -L/usr/local/lib rtl/identifiable.hpp: In member function 'unsigned int rel::identifiable::id() const': rtl/identifiable.hpp:22: error: cast from 'char*' to 'unsigned int' loses precision make: *** [test2] Error 1 ===========================================
This actually is just a warning in VC7, and never came up in MinGW, so I didn't pay much attention to it. But why wouldn't it be able to cast, is it a 32 or 64-bit system? Could you modify your copy of identifiable.hpp, and replace: return reinterpret_cast<unsigned int>(p.get()); with return p.get() - (char*)0; This may fix the problem (at least it removes the warning in vc71). Regards, Arkadiy PS: my results for RTL (MinGW with optimization) --3094ms. My PC has a 1.5 GHz processor.

The results are in: I'm running Linux FC4 on x86_64 The RTL results are quite amazing with -O3 optimization 1000000 records added 2000000 records added That took just 320ms RML RTL -O3 1260ms 320ms -O2 3200 ms 780 ms w/o 21230ms 13760ms Maybe there is something hampering RML 64-bit performance, as my best RML result on a 3GHz box doesn't differ much from Calum's 1.7GHz laptop ! I would like to see a more complex test with insert, select and delete, e.g. something like the resorce reservation system in http://www.infosys.tuwien.ac.at/NewsCache/pstl.html#evaluation where you have insertion, iteration, lookup/select and deletion On 10/9/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Jose" <jmalv04@gmail.com> wrote
===========================================
g++ -O6 arkadiy.cpp -o test2 -Irtl -I/usr/local/include/boost-1_33 -L/usr/local/lib rtl/identifiable.hpp: In member function 'unsigned int rel::identifiable::id() const': rtl/identifiable.hpp:22: error: cast from 'char*' to 'unsigned int' loses precision make: *** [test2] Error 1 ===========================================
This actually is just a warning in VC7, and never came up in MinGW, so I didn't pay much attention to it. But why wouldn't it be able to cast, is it a 32 or 64-bit system?
Could you modify your copy of identifiable.hpp, and replace:
return reinterpret_cast<unsigned int>(p.get());
with
return p.get() - (char*)0;
This may fix the problem (at least it removes the warning in vc71).
Regards, Arkadiy
PS: my results for RTL (MinGW with optimization) --3094ms. My PC has a 1.5 GHz processor.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

PS: my results for RTL (MinGW with optimization) --3094ms. My PC has a 1.5 GHz processor.
I should warn you both that gcc's ability to optimize RML is quite poor. I had to move to gcc 4.0.1 to get decent performance. VC7.1 does a much better job, which is where my number came from. I also don't really understand the purpose of this "benchmark". The test itself does nothing useful other than to probe the index 1000000 times. It would be better with a real-life problem. Calum

My results are using gcc 4.0.1 I agree the benchmark is quite silly so maybe each of you should come up with a mixed-operation benchmark that exploits your library well and then we can run both benchmarks This is not intended to pit one library against the other but to gain some insights on which approaches bring best performance I think it would also be useful to have RTL's basic distance example shown in RML and then benchmark that with 10k+ points On 10/9/05, Calum Grant <calum@visula.org> wrote:
PS: my results for RTL (MinGW with optimization) --3094ms. My PC has a 1.5 GHz processor.
I should warn you both that gcc's ability to optimize RML is quite poor. I had to move to gcc 4.0.1 to get decent performance. VC7.1 does a much better job, which is where my number came from.
I also don't really understand the purpose of this "benchmark". The test itself does nothing useful other than to probe the index 1000000 times. It would be better with a real-life problem.
Calum
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

"Jose" <jmalv04@gmail.com> wrote
I think it would also be useful to have RTL's basic distance example shown in RML and then benchmark that with 10k+ points
Well, I guess it would be quite difficult to come up with exact locations of 10k+ cities (unless somebody already have this data). That's why my, I agree, quite silly example, was initially designed to mimique this problem (http://lists.boost.org/Archives/boost/2005/10/94890.php). All that needs to be done is to complicate the condition, so that such things as merge or nested loops join are not possible. I think something like "abs(c1 - c2) < 1" should do fine. I already suggested this before. This example has to show the strongest point of RTL. It's quite time-consumming to go through every possible pair evaluating the condition (selection over cross-product). With RTL it's possible to do it only once, create an index on top of this expression, and then all the subsequent queries would execute orders of magnitude faster. The updates to the table would not require to fully rebuild the index -- it would be updated incrementally. Regards, Arkadiy

On 10/10/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Jose" <jmalv04@gmail.com> wrote
I think it would also be useful to have RTL's basic distance example shown in RML and then benchmark that with 10k+ points
Well, I guess it would be quite difficult to come up with exact locations of 10k+ cities (unless somebody already have this data).
The list below is up to date and has 20K+ coordinates that show the location of IP Autonomous Systems (basically major ISPs). http://netgeo.caida.org/aslatlong.txt One query idea that comes to my mind is sorting the AS locations by the number of other AS that are in a 5 Km radius and limiting this to the top 500. That would give a "rough" idea of which locations in the world that have the highest IP connectivity. The name, so that is understandable, should be something like "CEDEL-MRS, LONDON, GB" (example taken from concatenating fields for CEDEL-MRS AS name). This file changes regularly, as the net topology changes, so a second query would be the time it takes, having the current file in memory, to read the updated file and run the updated query (and maybe show the list of locations that have gained the most positions in the rank list)

"Jose" <jmalv04@gmail.com> wrote
The list below is up to date and has 20K+ coordinates that show the location of IP Autonomous Systems (basically major ISPs).
http://netgeo.caida.org/aslatlong.txt
One query idea that comes to my mind is sorting the AS locations by the number of other AS that are in a 5 Km radius and limiting this to the top 500. That would give a "rough" idea of which locations in the world that have the highest IP connectivity. The name, so that is understandable, should be something like "CEDEL-MRS, LONDON, GB" (example taken from concatenating fields for CEDEL-MRS AS name).
Here is the RTL solution (please find it attached). The result on my PC (1.5GHz, VC71, release mode, no optimization), for the query only, is 28 sec. G++ does a much poorer job this time, even with the optimization -- 64 sec. I had to fix some performance issues, so please download the latest version of RTL from the file vault: http://www.boost-consulting.com/vault/index.php?&direction=0&order=&director y=RTL
This file changes regularly, as the net topology changes, so a second query would be the time it takes, having the current file in memory, to read the updated file and run the updated query (and maybe show the list of locations that have gained the most positions in the rank list)
This is to follow. Regards, Arkadiy begin 666 main.cpp M(VEN8VQU9&4@/&-T:6UE/@T*(VEN8VQU9&4@/&EO<W1R96%M/@T*(VEN8VQU M9&4@/&9S=')E86T^#0HC:6YC;'5D92 \8V%S<V5R=#X-"B-I;F-L=61E(#QS M=')I;F<^#0HC:6YC;'5D92 \8VUA=&@^#0HC:6YC;'5D92 \:6]M86YI<#X- M"B-I;F-L=61E(#QB;V]S="]T;VME;FEZ97(N:'!P/@T*(VEN8VQU9&4@/&)O M;W-T+VQE>&EC86Q?8V%S="YH<' ^#0HC:6YC;'5D92 \8F]O<W0O='EP96]F M+W1Y<&5O9BYH<' ^#0HC:6YC;'5D92 \=&%B;&4N:'!P/@T*(VEN8VQU9&4@ M/'5T:6QS+FAP<#X-"B-I;F-L=61E(#QR96YA;64N:'!P/@T*(VEN8VQU9&4@ M/')A;F=E7VIO:6XN:'!P/@T*(VEN8VQU9&4@/'-E;&5C=&EO;BYH<' ^#0HC M:6YC;'5D92 \9W)O=7!B>2YH<' ^#0HC:6YC;'5D92 \9W)O=7!B>5]F=6YC M=&]R<RYH<' ^#0HC:6YC;'5D92 \:V5Y7VEN9&5X+FAP<#X-"@T*=7-I;F<@ M;F%M97-P86-E('-T9#L-"G5S:6YG(&YA;65S<&%C92!B;V]S=#L-"G5S:6YG M(&YA;65S<&%C92!R96P[#0H-"B\O+R\O+R\O+R\O#0H-"G-T871I8R!C;VYS M="!D;W5B;&4@<&D@/2 S+C$T,34Y,C<[#0IS=&%T:6,@8V]N<W0@9&]U8FQE M(')A9&EU<R ](#8S-S$N,#L@+R\@:6X@:VTN#0H-"F-L87-S($5A<G1H0V]O M<F0@#0I[#0IP=6)L:6,Z#0H@(" @("!%87)T:$-O;W)D*&1O=6)L92!L870L M(&1O=6)L92!L;F<I(#H@;&%T7R@H;&%T+S$X,"XI*G!I*2P@;&YG7R@H;&YG M+S$X,"XI*G!I*2![?0T*(" @(" @16%R=&A#;V]R9"@I(#H@;&%T7R@P+BDL M(&QN9U\H,"XI('M]#0H@(" @("!D;W5B;&4@9&ES=&%N8V4H8V]N<W0@16%R M=&A#;V]R9"8@;W1H97(I(&-O;G-T#0H@(" @("N(')A9&EU<RIA8V]S M*',I.PT*(" @(" @?0T*(" @(" @9&]U8FQE(&=E=$QA=&ET=61E*"D@8V]N M<W0@>W)E='5R;B!L871?.WT-"B @(" @(&1O=6)L92!G971,;VYG:71U9&4H M*2!C;VYS="![<F5T=7)N(&QN9U\[?0T*<')I=F%T93H-"B @(" @(&1O=6)L M92!L871?.PT*(" @(" @9&]U8FQE(&QN9U\[#0I].PT*#0HO+R\O+R\O+R\O M+PT*#0I"3T]35%]25$Q?1$5&24Y%7T-/3%5-3BAI;G0L(&YU;6)E<BD[#0I" M3T]35%]25$Q?1$5&24Y%7T-/3%5-3BAD;W5B;&4L(&QA=&ET=61E*3L-"D)/ M3U-47U)43%]$149)3D5?0T],54U.*&1O=6)L92P@;&]N9VET=61E*3L-"D)/ M3U-47U)43%]$149)3D5?0T],54U.*'-T<FEN9RP@;F%M92D[#0I"3T]35%]2 M5$Q?1$5&24Y%7T-/3%5-3BAS=')I;F<L(&-I='DI.PT*0D]/4U1?4E1,7T1% M1DE.15]#3TQ534XH<W1R:6YG+"!S=&%T92D[#0I"3T]35%]25$Q?1$5&24Y% M7T-/3%5-3BAS=')I;F<L(&-O=6YT<GDI.PT*0D]/4U1?4E1,7T1%1DE.15]# M3TQ534XH<W1R:6YG+"!I<V]?8V]U;G1R>2D[#0I"3T]35%]25$Q?1$5&24Y% M7T-/3%5-3BAS=')I;F<L(&-O;G1I;F5N="D[#0H-"G-T<G5C="!M>6EN9F\@ M.B!T86)L95]I;F9O/ T*(" @(&UP;#HZ=F5C=&]R.3QN=6UB97(L(&QA=&ET M=61E+"!L;VYG:71U9&4L(&YA;64L(&-I='DL('-T871E+"!C;W5N=')Y+"!I M<V]?8V]U;G1R>2P@8V]N=&EN96YT/BP-"B @("!M<&PZ.G9E8W1O<C$\;G5M M8F5R/@T*/GM].PT*#0IT>7!E9&5F('1A8FQE/&UY:6YF;SX@;7ET86)L93L- M"G1Y<&5D968@;7ET86)L93HZ=F%L=65?='EP92!M>71U<&QE.PT*#0IS=')U M8W0@83L-"G1Y<&5D968@86QI87,\;G5M8F5R+"!A/B!N=6UB97)?83L-"G1Y M<&5D968@86QI87,\;&%T:71U9&4L(&$^(&QA=&ET=61E7V$[#0IT>7!E9&5F M(&%L:6%S/&QO;F=I='5D92P@83X@;&]N9VET=61E7V$[#0IT>7!E9&5F(&%L M:6%S/&YA;64L(&$^(&YA;65?83L-"@T*+R\O+R\O+R\O+PT*#0IS=')U8W0@ M9&ES=&%N8V5?;&5S<U]T:&%N#0I[#0H@(" @9&ES=&%N8V5?;&5S<U]T:&%N M*&1O=6)L92!D*2 Z(&1?*&0I('M]#0H-"B @("!T96UP;&%T92 \8VQA<W,@ M270^(&)O;VP@;W!E<F%T;W(H*2AC;VYS="!)="8@:70I(&-O;G-T#0H@(" @ M>PT*(" @(" @("!I9B H:71;;G5M8F5R*"E=(#T](&ET6VYU;6)E<E]A*"E= M*0T*(" @(" @(" @(" @<F5T=7)N(&9A;'-E.PT*#0H@(" @(" @($5A<G1H M0V]O<F0@96,Q*&ET6VQA=&ET=61E*"E=+"!I=%ML;VYG:71U9&4H*5TI.PT* M(" @(" @("!%87)T:$-O;W)D(&5C,BAI=%ML871I='5D95]A*"E=+"!I=%ML M;VYG:71U9&5?82@I72D[#0H@(" @(" @(')E='5R;B!E8S$N9&ES=&%N8V4H M96,R*2 \(&1?.PT*(" @('T@(" @(" @(" @( T*(" @(&1O=6)L92!D7SL- M"GT[#0H-"B\O+R\O+R\O+R\-"@T*8VQA<W,@9FEN9%]L871I='5D95]A=%]D M:7-T86YC90T*>PT*<'5B;&EC.@T*(" @(&9I;F1?;&%T:71U9&5?871?9&ES M=&%N8V4H9&]U8FQE(&0I#0H@(" @(" @(#H@9%\H9" J('!I("\@,C P,# I M#0H@(" @>WT-"@ET96UP;&%T93QC;&%S<R!)="P@8VQA<W,@5&%B;&4^#0H) M"71Y<&5N86UE(%1A8FQE.CIC;VYS=%]I=&5R871O<B!O<&5R871O<B@I*&-O M;G-T($ET)B!I="P@8V]N<W0@5&%B;&4F('0I(&-O;G-T#0H)>PT*(" @(" @ M("!R;W<\;7!L.CIV96-T;W(Q/&QA=&ET=61E7V$^(#X@<W5B*&ET6VQA=&ET M=61E*"E=("L@9%\I.PT*"0ER971U<FX@="YL;W=E<E]B;W5N9"AS=6(I.PT* M"7T-"G!R:79A=&4Z#0H@(" @9&]U8FQE(&1?.PT*?3L-"@T*+R\O+R\O+R\O M+PT*#0IT96UP;&%T93QC;&%S<R!4/@T*=F]I9"!L;V%D7W1A8FQE*%0F('0L M(&ES=')E86TF(&EN*0T*>PT*(" @(&EN="!C;G0@/2 P.PT*#0H@(" @=VAI M;&4@*"%I;BYE;V8H*2D-"B @("![#0H@(" @(" @('1R>0T*(" @(" @("![ M#0H@(" @(" @(" @("!S=')I;F<@;&EN93L-"B @(" @(" @(" @(&=E=&QI M;F4H:6XL(&QI;F4I.PT*#0H@(" @(" @(" @("!I9B H;&EN95LP72 ]/2 G M(R<@?'P@;&EN92YE;7!T>2@I*0T*(" @(" @(" @(" @(" @(&-O;G1I;G5E M.PT*#0H@(" @(" @(" @("!M>71U<&QE('1P.PT*#0H@(" @(" @(" @("!T M;VME;FEZ97(\8VAA<E]S97!A<F%T;W(\8VAA<CX@/B!T;VME;G,H;&EN92P@ M8VAA<E]S97!A<F%T;W(\8VAA<CXH(EQT(BDI.PT*(" @(" @(" @(" @=&]K M96YI>F5R/&-H87)?<V5P87)A=&]R/&-H87(^(#XZ.FET97)A=&]R(&ET(#T@ M=&]K96YS+F)E9VEN*"D[#0H@(" @(" @(" @("!T<%MN=6UB97(H*5T@/2!L M97AI8V%L7V-A<W0\:6YT/B@J:70K*RD[#0H@(" @(" @(" @("!T<%ML871I M='5D92@I72 ](&QE>&EC86Q?8V%S=#QD;W5B;&4^*"II="LK*3L-"B @(" @ M(" @(" @('1P6VQO;F=I='5D92@I72 ](&QE>&EC86Q?8V%S=#QD;W5B;&4^ M*"II="LK*3L-"B @(" @(" @(" @('1P6VYA;64H*5T@/2 J:70K*SL-"B @ M(" @(" @(" @('1P6V-I='DH*5T@/2 J:70K*SL-"B @(" @(" @(" @('1P M6W-T871E*"E=(#T@*FET*RL[#0H@(" @(" @(" @("!T<%MC;W5N=')Y*"E= M(#T@*FET*RL[#0H@(" @(" @(" @("!T<%MI<V]?8V]U;G1R>2@I72 ]("II M="LK.PT*(" @(" @(" @(" @='!;8V]N=&EN96YT*"E=(#T@*FET*RL[#0H@ M(" @(" @(" @(" -"B @(" @(" @(" @('0N:6YS97)T*'1P*3L-"@T*(" @ M(" @(" @(" @+R]I9B H*RMC;G0@/3T@-3 P*2 -"B @(" @(" @(" @("\O M(" @(&)R96%K.PT*(" @(" @("!]#0H@(" @(" @(&-A=&-H("AC;VYS="!B M861?;&5X:6-A;%]C87-T)B!E*0T*(" @(" @("![#0H@(" @(" @(" @("!C M;W5T(#P\(&4N=VAA="@I(#P\(&5N9&P[#0H@(" @(" @('T-"B @("!]#0I] M#0H-"B\O+R\O+R\O+R\-"@T*;6%I;B@I#0I[#0H@(" @:69S=')E86T@:6XH M(F%S;&%T;&]N9RYT>'0B*3L-"B @("!A<W-E<G0H:6XN:7-?;W!E;B@I*3L- M"@T*(" @(&UY=&%B;&4@=#L-"B @("!L;V%D7W1A8FQE*'0L(&EN*3L-"@T* M(" @(&-O=70@/#P@8V]U;G0H="D@/#P@(B!R96-O<F1S(&QO861E9"(@/#P@ M96YD;#L-"@T*(" @(&-L;V-K7W0@=# @/2!C;&]C:R@I.PT*#0H@(" @+R\@ M:6YD97@@=&AE('1A8FQE(&]N(&QA=&ET=61E#0H@(" @0D]/4U1?05543RAT M7V)Y7VQA="P-"B @(" @(" @*&ME>5]I;F1E>#QM<&PZ.G9E8W1O<C(\;&%T M:71U9&5?82P@;G5M8F5R7V$^(#XH875T;U]R96YA;64\83XH="DI*0T*(" @ M(" @(" I.PT*#0H@(" @+R\@<V5L9BUJ;VEN('1H92!T86)L92!R96UO=FEN M9R!P86ER<R!T:&%T(&%R92!?;V)V:6]U<VQY7R!F87)T:&5R(&%W87D@=&AA M;B U(&MM#0H@(" @0D]/4U1?05543RAR:BP-"B @(" @(" @<F%N9V5?:F]I M;BAT+"!T7V)Y7VQA="P@9FEN9%]L871I='5D95]A=%]D:7-T86YC92@M-2DL M(&9I;F1?;&%T:71U9&5?871?9&ES=&%N8V4H-2DI#0H@(" @(" @("D[#0H- M"B @(" O+R!D;R!T:&4@<')O<&5R('-E;&5C=&EO;@T*(" @($)/3U-47T%5 M5$\H<V5L+" -"B @(" @(" @<V5L96-T:6]N*')J+"!D:7-T86YC95]L97-S M7W1H86XH-2DI#0H@(" @(" @("D[#0H-"B @(" O+R!G970@=&AE(&YU;6)E M<B!O9B!!4W,-"B @("!"3T]35%]!551/*&=B>2P-"B @(" @(" @*&=R;W5P M8GD\,2P@;7!L.CIV96-T;W(Q/&-O=6YT97(^(#XH<V5L*2D-"B @(" @(" @ M*3L-"@T*(" @("\O('-O<G0@;VX@;G5M8F5R(&]F($%3<PT*(" @($)/3U-4 M7T%55$\H:61X+ T*(" @(" @(" H:V5Y7VEN9&5X/&UP;#HZ=F5C=&]R,CQC M;W5N=&5R+"!N=6UB97(^(#XH9V)Y*2D-"B @(" @(" @*3L-"@T*(" @("\O M(&%D9"!B86-K('1H92!!4R!N86UE( T*(" @($)/3U-47T%55$\H<F5S=6QT M+ T*(" @(" @("!E<75A;%]J;VEN/&UP;#HZ=F5C=&]R,3QN=6UB97(^(#XH M:61X+"!A=71O7W)E;F%M93QA/BAT*2D-"B @(" @(" @*3L-"@T*(" @("\O M(&=E="!L87-T(#4P, T*(" @($)/3U-47T%55$\H8F5G:6XL(')E<W5L="YB M96=I;B@I*3L-"B @("!I;G0@8VYT(#T@-3 P.PT*#0H@(" @9F]R("A"3T]3 M5%]!551/*&ET+"!R97-U;'0N96YD*"DI.R M+6ET("$](&)E9VEN("8F(&-N M="TM("$](# [("D-"B @("![#0H@(" @(" @(&-O=70@/#P@:71;;G5M8F5R M*"E=(#P\("=<="<[#0H@(" @(" @(&-O=70@/#P@<V5T=R@U,"D@/#P@:71; M;F%M95]A*"E=(#P\("=<="<[#0H@(" @(" @(&-O=70@/#P@:71;8V]U;G1E M<B@I72 \/" G7'0G.PT*(" @(" @("!C;W5T(#P\(&5N9&P[#0H@(" @?0T* M#0H@(" @8VQO8VM?="!T,2 ](&-L;V-K*"D[#0H@(" @8V]U=" \/" B5&AE M('%U97)Y('1O;VL@(B \/" H=#$M=# I+T-,3T-+4U]015)?4T5#(#P\("(@ C<V5C(B \/"!E;F1L.PT*#0H@(" @<F5T=7)N(# [#0I]#0H` ` end

On 10/10/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Jose" <jmalv04@gmail.com> wrote
I think it would also be useful to have RTL's basic distance example shown in RML and then benchmark that with 10k+ points [...]
(http://lists.boost.org/Archives/boost/2005/10/94890.php). All that needs
to be done is to complicate the condition, so that such things as merge or nested loops join are not possible. I think something like "abs(c1 - c2) < 1" should do fine. I already suggested this before.
Can you explain "such tings as merge or nested loop joins are not possible" ? I'm not sure I understand

On 10/10/05, Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Jose" <jmalv04@gmail.com> wrote
I think it would also be useful to have RTL's basic distance example shown in RML and then benchmark that with 10k+ points [...]
(http://lists.boost.org/Archives/boost/2005/10/94890.php). All that needs
to be done is to complicate the condition, so that such things as merge or nested loops join are not possible. I think something like "abs(c1 - c2) < 1" should do fine. I already suggested this before.
Can you explain "such tings as merge or nested loop joins are not
"Jose" <jmalv04@gmail.com> wrote possible"
?
There are different ways to join tables. In the example discussed both tables are sorted the same way (it's actually the same table). To join such tables on the equal condition one can use merge, which is O(n+m) algorithm. If the condition is more complicated, merge is not possible, and one has to consider every possible combination (selection over cross-product), which is a O(n*m) operation, and obviously, depending on the table size, orders of magnitude slower. BTW, the file you provided is very interesting. I played with it yesturday, but so far my results are pretty slow (no wonder -- I have to consider more than 400,000,000 possible combinations). One of possible reasons is the lack of co-processor on my PC (quite a bit of math is done), but I also found some RTL inefficiencies (as I said, we haven't spend a lot of time on the optimization yet). I will do some more research and then provide the results. Will probably take a few days to a week. One thing that I also hope to show is that, even though the initial calculations may be slow, the subsequent calculations on the updated table will be very fast. Regards, Arkadiy

BTW, the file you provided is very interesting. I played with it yesturday, but so far my results are pretty slow (no wonder -- I have to consider more than 400,000,000 possible combinations). One of possible reasons is the lack of co-processor on my PC (quite a bit of math is done), but I also found some RTL inefficiencies (as I said, we haven't spend a lot of time on the optimization yet). I will do some more research and then provide the results. Will probably take a few days to a week.
You could instead sort on the square of the distance - saving you one square root per iteration, and since you don't need square root, you can just work with integers. I'm just intrigued how you are going to fit 400,000,000 items into an index in memory!! Cheers, Calum

"Calum Grant" <calum@visula.org> wrote
BTW, the file you provided is very interesting. I played with it yesturday, but so far my results are pretty slow (no wonder -- I have to consider more than 400,000,000 possible combinations). One of possible reasons is the lack of co-processor on my PC (quite a bit of math is done), but I also found some RTL inefficiencies (as I said, we haven't spend a lot of time on the optimization yet). I will do some more research and then provide the results. Will probably take a few days to a week.
You could instead sort on the square of the distance - saving you one square root per iteration, and since you don't need square root, you can just work with integers. I'm just intrigued how you are going to fit 400,000,000 items into an index in memory!!
I never wanted to do this -- I index the resulting groupby, not the crossproduct. But you are right I can do something more intellegent -- this particular task does allow this. Regards, Arkadiy

"Calum Grant" <calum@visula.org> wrote
// Print only 10 results output_results(limit(select( (my_table, my_table), col<0,0>() == col<1,0>() ), 10));
So, you don't seem to iterate the whole selection, only first ten results. Since we both evaluate the expressions lazily, this doesn't look like a right test to me. If you apply the merge-join, such a query would take the same time on a 10-row table or on a 1000000-row table, wouldn't it? Regards, Arkadiy

// Print only 10 results output_results(limit(select( (my_table, my_table), col<0,0>() == col<1,0>() ), 10));
So, you don't seem to iterate the whole selection, only first ten results. Since we both evaluate the expressions lazily, this doesn't look like a right test to me. If you apply the merge-join, such a query would take the same time on a 10-row table or on a 1000000-row table, wouldn't it?
I iterate the whole selection twice, as you can see by the output of the numbers "1000000" and "2000000" which shows the number of items it iterated. It took just 300ms to iterate them. The limit() is not part of the benchmark, it just lets you know that the data is real. Outputting 2000000 results to a terminal would only measure the speed of writing to a terminal. So how long did RTL take? Calum

"Calum Grant" <calum@visula.org> wrote
Here is the output on my 1.7GHz laptop:
1000000 ((0),(0)) ((1),(1)) ((2),(2)) ((3),(3)) ((4),(4)) ((5),(5)) ((6),(6)) ((7),(7)) ((8),(8)) ((9),(9)) Total 10 results 2000000 That took just 1687ms ^^^^^^^^^^^^^^^^^^^ Press any key to continue
These results are so unbelievably impressive, that I had to see them with my own eyes. So I downloaded your library from http://visula.org/relational. My PS has a 1.5 Ghz processor, and I used VC71 public edition (no optimization), release mode. Unfortunately my results were slightly different from yours: 1000000 ((0),(0)) ((1),(1)) ((2),(2)) ((3),(3)) ((4),(4)) ((5),(5)) ((6),(6)) ((7),(7)) ((8),(8)) ((9),(9)) Total 10 results 2000000 That took just 35453ms ^^^^^^^^^^^^^^^^^^ Press any key to continue This is a little less impressive :-( What am I missing? Regards, Arkadiy PS: The RTL (after modifications I mentioned before) was more than twice faster: 1000000 records added 2000000 records added That took just 14906ms ^^^^^^^^^^^^^^^^^^ Press any key to continue Here is the modified text (again, I am using current Boost CVS): #include <table_delta.hpp> #include <utils.hpp> #include <selection_delta.hpp> #include <key_index_delta.hpp> #include <crossproduct_delta.hpp> #include <rename_delta.hpp> #include <boost/lambda/lambda.hpp> #include <boost/typeof/typeof.hpp> #include <expression_registry.hpp> #include <transaction.hpp> #include <cstdlib> #include <merge_delta.hpp> #include <windows.h> #include <ctime> using namespace boost; using namespace boost::lambda; BOOST_RTL_DEFINE_COLUMN(int, c1); struct my_info : rel::table_info< mpl::vector<c1>
{};
typedef rel::table<my_info> my_table; typedef my_table::value_type my_tuple; struct a; typedef rel::alias<c1, a> c2; main() { using namespace std; clock_t t0 = clock(); my_table t; t.reserve(2000000); int i; for (i = 0; i < 1000000; ++i) t.insert(my_tuple(i + 1)); BOOST_AUTO(t2, rel::auto_rename<a>(t)); BOOST_AUTO(mr, rel::merge<1>(t, t2)); cout << count(mr) << " records added" << endl; for (; i < 2000000; ++i) t.insert(my_tuple(i + 1)); cout << count(mr) << " records added" << endl; clock_t t1 = clock(); std::cout << "That took just " << (1000*(t1-t0))/CLOCKS_PER_SEC << "ms\n"; return 0; }
participants (6)
-
Andreas Pokorny
-
Arkadiy Vertleyb
-
Calum Grant
-
Jose
-
Larry Evans
-
Rob Meijer