Benchmarking several ASCII-table-generator modules

UPDATE #1 2014-07-11: Added Catmandu::Exporter::Table. This module is not exactly lightweight, so I will not consider it for usage in Perinci::CmdLine::Lite, but it's interesting to benchmark anyway.

UPDATE #2 2014-07-11: Nudged by me, Jakob extracted the table-generating
functionality of Catmandu::Exporter::Table into its own module Text::MarkdownTable. This module depends on nothing but Moo. Great job Jakob. Although for my particular project Perinci::CmdLine::Lite, I declared that Moo is a bit too heavy, and so excluded it. :)

UPDATE #3 2014-07-11: Added more modules to the mix: Text::FormatTable, Text::Table::Tiny. Excluded more modules: Data::ShowTable, Text::UnicodeBox::Table.

UPDATE #4 2014-07-11: Excluded 2 more modules: Table::Simple, Text::SimpleTable::AutoWidth

For my upcoming Perinci::CmdLine::Lite module, I want to use a small and fast ASCII-table-generator module to replace Text::ANSITable. That module, which I wrote, is featureful (and is currently the only CPAN module which can handle ANSI colors, wide characters, multiline text), but is horribly slow, about an order or two of magnitude slower than the other ASCII table modules because it needs to parse ASCII escape color codes in text as well as counting visual widths of characters. A C implementation for that is needed to really speed it up. When we don't care about colors (or wide characters), it's better to use the simpler and faster modules.

Here are several which I included in this benchmark:

Some of the other modules are not included:

  • Text::SimpleTable, because it doesn't calculate column widths for us, we need to supply widths ourselves.

  • Data::ShowTable, because of the interface. The interface is, how should I say it, unfitting for the simple case of formatting an array of arrays. You have to construct a "row subroutine" which must react to a rewindable argument, and iterate the rows yourself. Plus the module is designed to print to STDOUT instead of returning the rendered string (you can use IO::Scalar or Capture::Tiny, but...)

  • Text::UnicodeBox::Table because it's designed to use Unicode non-ASCII border characters.

  • Table::Simple (currently skipping Moose-using modules because startup overhead is too big)

  • Text::SimpleTable::AutoWidth (currently skipping Moose-using modules because startup overhead is too big)

Below is the result (the code is here):

Text::ANSITable:
.--------+--------+--------.
| col1   | col2   | col3   |
+--------+--------+--------+
| row1.1 | row1.2 | row1.3 |
| row2.1 | row2.2 | row2.3 |
| row3.1 | row3.2 | row3.3 |
| row4.1 | row4.2 | row4.3 |
| row5.1 | row5.2 | row5.3 |
`--------+--------+--------'

Text::ASCIITable:
.--------------------------.
| col1 | col2 | col3 |
+--------+--------+--------+
| row1.1 | row1.2 | row1.3 |
| row2.1 | row2.2 | row2.3 |
| row3.1 | row3.2 | row3.3 |
| row4.1 | row4.2 | row4.3 |
| row5.1 | row5.2 | row5.3 |
'--------+--------+--------'

Text::FormatTable:
col1 |col2 |col3
row1.1|row1.2|row1.3
row2.1|row2.2|row2.3
row3.1|row3.2|row3.3
row4.1|row4.2|row4.3
row5.1|row5.2|row5.3

Text::MarkdownTable:
| col1 | col2 | col3 |
|--------|--------|--------|
| row1.1 | row1.2 | row1.3 |
| row2.1 | row2.2 | row2.3 |
| row3.1 | row3.2 | row3.3 |
| row4.1 | row4.2 | row4.3 |
| row5.1 | row5.2 | row5.3 |

Text::Table:
col1 col2 col3
row1.1 row1.2 row1.3
row2.1 row2.2 row2.3
row3.1 row3.2 row3.3
row4.1 row4.2 row4.3
row5.1 row5.2 row5.3

Text::Table::Tiny:
+--------+--------+--------+
| col1 | col2 | col3 |
+--------+--------+--------+
| row1.1 | row1.2 | row1.3 |
| row2.1 | row2.2 | row2.3 |
| row3.1 | row3.2 | row3.3 |
| row4.1 | row4.2 | row4.3 |
| row5.1 | row5.2 | row5.3 |
+--------+--------+--------+
Text::TabularDisplay:
+--------+--------+--------+
| col1 | col2 | col3 |
+--------+--------+--------+
| row1.1 | row1.2 | row1.3 |
| row2.1 | row2.2 | row2.3 |
| row3.1 | row3.2 | row3.3 |
| row4.1 | row4.2 | row4.3 |
| row5.1 | row5.2 | row5.3 |
+--------+--------+--------+

0tiny(1x1) table:
Rate Text::ANSITable Text::ASCIITable Text::Table Text::MarkdownTable Text::FormatTable Text::TabularDisplay Text::Table::Tiny
Text::ANSITable 2732/s -- -64% -79% -83% -86% -91% -96%
Text::ASCIITable 7565/s 177% -- -43% -53% -60% -74% -89%
Text::Table 13273/s 386% 75% -- -17% -30% -55% -81%
Text::MarkdownTable 15999/s 486% 111% 21% -- -16% -45% -78%
Text::FormatTable 19040/s 597% 152% 43% 19% -- -35% -73%
Text::TabularDisplay 29191/s 969% 286% 120% 82% 53% -- -59%
Text::Table::Tiny 71521/s 2518% 845% 439% 347% 276% 145% --

1small(3x5) table:
Rate Text::ANSITable Text::ASCIITable Text::FormatTable Text::Table Text::MarkdownTable Text::TabularDisplay Text::Table::Tiny
Text::ANSITable 662/s -- -62% -83% -86% -89% -91% -98%
Text::ASCIITable 1744/s 163% -- -54% -64% -70% -77% -95%
Text::FormatTable 3828/s 478% 119% -- -20% -34% -50% -90%
Text::Table 4799/s 624% 175% 25% -- -17% -37% -87%
Text::MarkdownTable 5784/s 773% 232% 51% 21% -- -24% -84%
Text::TabularDisplay 7658/s 1056% 339% 100% 60% 32% -- -79%
Text::Table::Tiny 36654/s 5433% 2002% 857% 664% 534% 379% --

2wide(30x5) table:
Rate Text::ANSITable Text::ASCIITable Text::FormatTable Text::Table Text::MarkdownTable Text::TabularDisplay Text::Table::Tiny
Text::ANSITable 78.5/s -- -59% -81% -89% -93% -94% -99%
Text::ASCIITable 189/s 141% -- -53% -73% -83% -85% -97%
Text::FormatTable 403/s 413% 113% -- -42% -64% -68% -95%
Text::Table 697/s 789% 269% 73% -- -38% -45% -91%
Text::MarkdownTable 1131/s 1341% 498% 181% 62% -- -10% -85%
Text::TabularDisplay 1260/s 1505% 566% 213% 81% 11% -- -83%
Text::Table::Tiny 7473/s 9423% 3851% 1756% 972% 561% 493% --

3long(3x300) table:
(warning: too few iterations for a reliable count)
Rate Text::ANSITable Text::ASCIITable Text::FormatTable Text::MarkdownTable Text::TabularDisplay Text::Table Text::Table::Tiny
Text::ANSITable 16.0/s -- -60% -81% -90% -91% -95% -99%
Text::ASCIITable 40.2/s 151% -- -52% -74% -76% -87% -97%
Text::FormatTable 84.6/s 427% 110% -- -46% -50% -72% -94%
Text::MarkdownTable 156/s 874% 288% 85% -- -8% -48% -90%
Text::TabularDisplay 169/s 954% 321% 100% 8% -- -44% -89%
Text::Table 301/s 1777% 649% 256% 93% 78% -- -80%
Text::Table::Tiny 1537/s 9474% 3718% 1716% 883% 808% 410% --

4large(30x300) table:
(warning: too few iterations for a reliable count)
(warning: too few iterations for a reliable count)
(warning: too few iterations for a reliable count)
Rate Text::ANSITable Text::ASCIITable Text::FormatTable Text::MarkdownTable Text::TabularDisplay Text::Table Text::Table::Tiny
Text::ANSITable 1.74/s -- -57% -82% -93% -93% -98% -99%
Text::ASCIITable 4.00/s 130% -- -59% -84% -85% -95% -98%
Text::FormatTable 9.88/s 468% 147% -- -60% -62% -88% -96%
Text::MarkdownTable 25.0/s 1338% 525% 153% -- -4% -70% -89%
Text::TabularDisplay 25.9/s 1391% 548% 163% 4% -- -69% -88%
Text::Table 82.5/s 4644% 1963% 735% 230% 218% -- -63%
Text::Table::Tiny 224/s 12766% 5494% 2165% 795% 763% 171% --

Conclusion (2014-07-11)

  • Text::Table::Tiny wins hands down and is my choice. It is very fast because it doesn't copy rows and uses a long sprintf() format to do the formatting of a whole row instead of formatting each cell separately and joining them into a row using join(). Actually earlier today I had the exact same idea *and* module name, but turned out somebody had already did it. Thanks, Creighton Higgins!

  • Text::ANSITable is very slow and is expected. I'm including it for comparison only.

  • Text::ASCIITable is surprisingly relatively slow, given that it doesn't do colors or wide characters or multilines. It shouldn't be much slower than, say, Text::Table or Text::TabularDisplay.

2 Comments

Leave a comment

About Steven Haryanto

user-pic A programmer (mostly Perl 5 nowadays). My CPAN ID: SHARYANTO. I'm sedusedan on perlmonks. My twitter is stevenharyanto (but I don't tweet much). Follow me on github: sharyanto.