First crate: ansi colors at compile-time

I publish my first crate tonight, comments or reviews are welcome:

color-print

The main code implementation is here:

color-print-proc-macro

The goal

I want to propose an alternative to the crate colored, because:

  • I find that the way to use it is a bit verbose;
  • Formatting is performed at runtime, while Rust offers procedural macros.

Reviews are welcome

If some nice people are interested for a quick review, here are my questions:

  • What are the first aids to perform on this project in your opinion ?
  • I'm new to procedural macros : comments about color-print-proc-macro are welcome;
  • Comments are welcome as well about the terminfo feature: the implementation is splitted between the two crates:
  • What could be improved or corrected in the README.md ?
  • A more general question: do you think this crate can be useful, or did I loose my time ?
2 Likes

I think it's a fun idea to do this at compile time — perhaps someone will appreciate it if they're working on an embedded systems and don't want to compile in extra code to do the coloring at runtime.

It's also just plain faster at runtime, since part of the processing is already done. But how much that actually matters in any given application is a matter for benchmarks and profilers.

1 Like

You gave me the idea to make a tiny benchmark, and I didn't expect so much difference:

use color_print::*; // v0.2.0
use colored::*; // v2.0.0

macro_rules! bench {
    ( $($tt:tt)* ) => {{
        let now = std::time::Instant::now();
        { $( $tt )* };
        println!("{:.2?}", now.elapsed());
    }};
}

fn main() {
    const N: usize = 10_000_000;

    print!("colored: ");
    bench! {
        for _ in 0..N {
            format!("{}{}{}", "blue".blue(), "red".red(), "yellow".yellow());
        }
    }

    print!("color-print 1: ");
    bench! {
        for _ in 0..N {
            format!("{}{}{}", cformat!("<b>blue"), cformat!("<r>red"), cformat!("<y>yellow"));
        }
    }

    print!("color-print 2: ");
    bench! {
        for _ in 0..N {
            cformat!("<b>blue<r>red<y>yellow");
        }
    }
}

Output:

colored: 5.74s
color-print 1: 1.79s
color-print 2: 240.02ms
1 Like

Are you sure your benchmark really doesn't optimize out the strings ? Maybe you should output the strings somewhere, e.g. to /dev/null ?

3 Likes

I followed your advice and yes, the results are far more reasonnable, thank you :slight_smile:

Here is the corrected version (with more tests):

use color_print::*; // v0.2.0
use colored::*; // v2.0.0

const N: usize = 10_000_000;
static mut BENCH_NR: usize = 1;

macro_rules! bench {
    ( $title:tt $($tt:tt)* ) => {{
        let now = std::time::Instant::now();
        for _ in 0..N { $($tt)* }
        eprintln!("{:2}. {:<14} {:>4} ms  {}",
            unsafe { BENCH_NR },
            format!("[{}]", $title),
            now.elapsed().as_millis(),
            stringify!($($tt)*)
        );
        unsafe { BENCH_NR += 1 }
    }};
}

fn main() {
    eprintln!("\nNb iterations per bench: {}\n", N);

    bench! { "native"      print!("blue red yellow"); }
    bench! { "native"      print!("{}", "blue"); }
    bench! { "native"      print!("{} {}", "blue", "red"); }
    bench! { "native"      print!("{} {} {}", "blue", "red", "yellow"); }
    bench! { "colored"     "b".blue(); "r".red(); "y".yellow(); }
    bench! { "colored"     print!("{}", "blue".blue()); }
    bench! { "colored"     print!("{}{}", "blue".blue(), "red".red()); }
    bench! { "colored"     print!("{}{}{}", "blue".blue(), "red".red(), "yellow".yellow()); }
    bench! { "color-print" cprint!("<b>blue<r>red<y>yellow"); }
    bench! { "color-print" cprint!("<b>A"); cprint!("<r>B"); cprint!("<y>C"); }
    bench! { "color-print" print!("{}", cformat!("<b>blue")); }
    bench! { "color-print" print!("{}{}", cformat!("<b>blue"), cformat!("<r>red")); }
    bench! { "color-print" print!("{}{}{}", cformat!("<b>A"), cformat!("<r>B"), cformat!("<y>C")); }
}

Output of cargo run --release >/dev/null:

Nb iterations per bench: 10000000

 1. [native]        393 ms  print! ("blue red yellow") ;
 2. [native]        382 ms  print! ("{}", "blue") ;
 3. [native]        604 ms  print! ("{} {}", "blue", "red") ;
 4. [native]        831 ms  print! ("{} {} {}", "blue", "red", "yellow") ;
 5. [colored]       574 ms  "b".blue() ; "r".red() ; "y".yellow() ;
 6. [colored]       603 ms  print! ("{}", "blue".blue()) ;
 7. [colored]       961 ms  print! ("{}{}", "blue".blue(), "red".red()) ;
 8. [colored]      1353 ms  print! ("{}{}{}", "blue".blue(), "red".red(), "yellow".yellow()) ;
 9. [color-print]   463 ms  cprint! ("<b>blue<r>red<y>yellow") ;
10. [color-print]  1157 ms  cprint! ("<b>A") ; cprint! ("<r>B") ; cprint! ("<y>C") ;
11. [color-print]   631 ms  print! ("{}", cformat! ("<b>blue")) ;
12. [color-print]  1045 ms  print! ("{}{}", cformat! ("<b>blue"), cformat! ("<r>red")) ;
13. [color-print]  1425 ms  print! ("{}{}{}", cformat! ("<b>A"), cformat! ("<r>B"), cformat! ("<y>C")) ;

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.