// llil3grt.cpp. // Inspired by llilgrt.pl: improve sort performance via a negative count. // g++ compile on Linux: // g++ -o llil3grt -std=c++20 -Wall -O3 llil3grt.cpp // g++ -o llil3grt -std=c++20 -fopenmp -Wall -O3 llil3grt.cpp // This g++ command also works with mingw C++ compiler (https://sourceforge.net/projects/mingw-w64) // that comes bundled with Strawberry Perl (C:\Strawberry\c\bin\g++.exe). // Example run: llil3grt tt1.txt tt2.txt tt3.txt >out.txt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static_assert(sizeof(size_t) == sizeof(int64_t), "size_t too small, need a 64-bit compile"); // ---------------------------------------------------------------------------- // Crude hack to see Windows Private Bytes in Task Manager by sleeping at // program end (see also sleep hack at end of main) // #include // #include // ---------------------------------------------------------------------------- typedef long long llil_int_type; // Note: all words in big1.txt, big2.txt, big3.txt are <= 6 chars in length // To use (limited length) fixed length strings uncomment the next line // big.txt max word length is 6 // long.txt max word length is 208 // Based on rough benchmarking, this short fixed string hack // is only worth trying for MAX_STR_LEN_L up to about 64 #define MAX_STR_LEN_L 6 #ifdef MAX_STR_LEN_L using str_type = std::array; #else using str_type = std::string; #endif using str_int_type = std::pair; using int_str_type = std::pair; using vec_str_int_type = std::vector; using vec_int_str_type = std::vector; using set_int_str_type = std::set; using map_str_int_type = std::map; // Mimic the Perl get_properties subroutine ---------------------------- // Limit line length and use ANSI C functions to try to boost performance #define MAX_LINE_LEN_L 255 static void get_properties( int nfiles, // in: the number of input files char* fname[], // in: the input file names map_str_int_type& hash_ret) // out: a hash of properties { FILE* fh; char line[MAX_LINE_LEN_L+1]; char* found; llil_int_type count; for (int i = 0; i < nfiles; ++i) { fh = ::fopen(fname[i], "r"); if (fh == NULL) { std::cerr << "Error opening '" << fname[i] << "' : errno=" << errno << "\n"; continue; } while ( ::fgets(line, MAX_LINE_LEN_L, fh) != NULL ) { found = ::strchr(line, '\t'); count = ::atoll( &line[found - line + 1] ); line[found - line] = '\0'; // word #ifdef MAX_STR_LEN_L str_type fixword { { '\0', '\0', '\0', '\0', '\0', '\0' } }; ::memcpy( fixword.data(), line, found - line ); hash_ret[fixword] -= count; #else hash_ret[line] -= count; #endif } ::fclose(fh); } } // --------------------------------------------------------------------- int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "usage: llil3grt file1 file2 ... >out.txt\n"; return 1; } #ifdef MAX_STR_LEN_L std::cerr << "llil3grt (fixed string length=" << MAX_STR_LEN_L << ") start\n"; #else std::cerr << "llil3grt start\n"; #endif #if defined(_OPENMP) std::cerr << " - openmp version\n"; #endif time_t tstart1 = ::time(NULL); clock_t cstart1 = ::clock(); // Create the hash of properties map_str_int_type hash_ret; get_properties(argc - 1, &argv[1], hash_ret); clock_t cend1 = ::clock(); double ctaken1 = (double) (cend1 - cstart1) / (double)CLOCKS_PER_SEC; std::cerr << "get_properties CPU time : " << ctaken1 << " secs\n"; clock_t cstart2 = ::clock(); // To avoid calling sort(), try creating an inverted std::set container // Note: negative count gives desired ordering (just like Perl GRT sort :) set_int_str_type myset; for ( auto const& kv : hash_ret ) { // Note: emplace_hint() was a lot faster than emplace() myset.emplace_hint( myset.end(), kv.second, kv.first ); } clock_t cend2s = ::clock(); // Output the (already sorted) std::set - no sort() function required! // Note: fix up negative count via -n.first #ifdef MAX_STR_LEN_L // If name is not NULL-terminated: for ( auto const& n : myset ) ::printf( "%.*s\t%lld\n", MAX_STR_LEN_L, n.second.data(), -n.first ); // If name is NULL-terminated: // for ( auto const& n : myset ) ::printf( "%s\t%lld\n", n.second.data(), -n.first ); // for ( auto const& n : myset ) std::cout << n.second.data() << '\t' << -n.first << '\n'; #else // Can try printf vs std::cout to see which is faster // for ( auto const& n : myset ) ::printf( "%s\t%lld\n", n.second.c_str(), -n.first ); for ( auto const& n : myset ) std::cout << n.second << '\t' << -n.first << '\n'; #endif clock_t cend2 = ::clock(); time_t tend2 = ::time(NULL); long ttaken = static_cast(::difftime(tend2, tstart1) + 0.5); double ctaken = (double) (cend2 - cstart1) / (double)CLOCKS_PER_SEC; double ctaken2s = (double) (cend2s - cstart2) / (double)CLOCKS_PER_SEC; double ctaken2o = (double) (cend2 - cend2s) / (double)CLOCKS_PER_SEC; std::cerr << "emplace set sort CPU time : " << ctaken2s << " secs\n"; std::cerr << "write stdout CPU time : " << ctaken2o << " secs\n"; std::cerr << "total CPU time : " << ctaken << " secs\n"; std::cerr << "total wall clock time : " << ttaken << " secs\n"; // Hack to see Private Bytes in Windows Task Manager (uncomment next line so process doesn't exit too quickly) // std::this_thread::sleep_for(std::chrono::milliseconds(90000000)); return 0; }