Reasons to prefer includes over subcomponents in ecpp
ecpp include
ecpp is the template language used in tntnet to join C++ with HTML. If you want to split your application into multiple ecpp files, you have two possibilities. You can either make it a subcomponent that you call using <& subcomponent>, or you can include the ecpp code using <%include>. Most people seem to prefere calling subcomponents, so if i prefere the other way, i should have good reasons.
If you create a subcomponent, you get an independent component that get called as a function. You can pass arguments to the component, but you don't have access to the scope of the calling component. The argument types are very limited you can only pass strings, or types that can be converted to strings. If your subcomponent takes arguments other than strings, ecpp will use tnt::stringTo<type> to convert the argument from a string to your type. The argument will always be a copy of a string, you can't pass by reference or a pointer to a string.
subcomponent example
So, if you want to use a subcomponent to printout parts from you own types, you need to pass the strings to the subcomponent. as example lets take a vector<string> and fill it with 100 strings. For printing we use a subcomponent called in a loop.
<html> <head> <title>include test</title> </head> <body> <h1>main</h1> <{ std::vector<std::string> vec; for(int i=0; i<100; i++) { std::ostringstream s; s << "string" << i; vec.push_back(s.str()); } }> <h2>print a vector of 100 strings</h2> % for(std::vector<std::string>::const_iterator it = vec.begin(); it < vec.end(); it++) { <& comp arg1=*it > % } </body> </html>
The subcomponent comp takes one string as argument, it is very simple.
<%args> arg1; </%args> <p>argument: <$ arg1 $></p>
include example
The include example is very similar, but instead of calling a function to printout the string, the included ecpp becomes the body of the for loop. That saves a function call for each iteration of the loop, but more important, we don't need to copy the string to a different component.
<html> <head> <title>include test</title> </head> <body> <h1>main</h1> <{ std::vector<std::string> vec; for(int i=0; i<100; i++) { std::ostringstream s; s << "string" << i; vec.push_back(s.str()); } }> <h2>print a vector of 100 strings</h2> % for(std::vector<std::string>::const_iterator it = vec.begin(); it < vec.end(); it++) { <%include> incl.ecpp </%include> % } </body> </html>
The included file is even more simple. Notice, that it prints out whatever it points to, there is no defenition what type it is, so it really depends on what file includes it. This way it is possible to use the same include file for may different ecpp components with differnet types, as long as the objects in the components have the same name used in the include file and have all required operators and members.
<p>outstr: <$ *it $></p>
Comparing
When talking about performance we should first consider if it does matter. To find out how large the overhead of the subcomponent variant is, i used siege to compare both components, the first, mapped to /comp, uses the subcomponent printout, the second uses include.
$ ./siege -c 100 -b -r 1000 'http://127.0.0.1:8000/comp' ** SIEGE 2.70 ** Preparing 100 concurrent users for battle. The server is now under siege.. done. Transactions: 100000 hits Availability: 100.00 % Elapsed time: 260.25 secs Data transferred: 35.95 MB Response time: 0.25 secs Transaction rate: 384.25 trans/sec Throughput: 0.14 MB/sec Concurrency: 97.20 Successful transactions: 100000 Failed transactions: 0 Longest transaction: 24.58 Shortest transaction: 0.00
$ ./siege -c 100 -b -r 1000 'http://127.0.0.1:8000/incl' ** SIEGE 2.70 ** Preparing 100 concurrent users for battle. The server is now under siege.. done. Transactions: 100000 hits Availability: 100.00 % Elapsed time: 112.77 secs Data transferred: 33.95 MB Response time: 0.11 secs Transaction rate: 886.76 trans/sec Throughput: 0.30 MB/sec Concurrency: 94.05 Successful transactions: 100000 Failed transactions: 0 Longest transaction: 13.64 Shortest transaction: 0.00
So the difference is quite large, i got more than twice as much requests/sec done when using the include variant. Of course the example application is very simple, but i do use small ecpp snippets all the time to print a few strings from my objects. If you for example what to share the HTML headers for all sites but want to change <title>, it would become much faster, if you use include instead of passing the title string to a subcomponent.
Also i take advantage of the fact that include is type agnostic, if i just like to call object.getText() with some surrounding HTML, it really doesn't matter, what type the object is, as long as it have a member funktion getText() that returns something that can be converted to a string.
Subcomponents also have it's application, if you have larger subroutines that acts independent from the calling component and is called by multiple components, a subcomponent is typically to prefere. The overhead of calling a subcomponent especially without parameters is not that large and you can benefit from a smaller binary size, since a subcomponent get only included once in the whole application.