diff --git a/benchmark/0019.formatting/CMakeLists.txt b/benchmark/0019.formatting/CMakeLists.txt deleted file mode 100644 index a09edf0a5..000000000 --- a/benchmark/0019.formatting/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.22) -project(benchmark_formatting CXX) - -include(FetchContent) - -# Fetch fmt library -FetchContent_Declare( - fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 10.2.1 -) -FetchContent_MakeAvailable(fmt) - -add_executable(benchmark.0019.formatting.format_vs_fmt ${CMAKE_CURRENT_LIST_DIR}/format_vs_fmt.cc) -target_include_directories(benchmark.0019.formatting.format_vs_fmt PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_compile_features(benchmark.0019.formatting.format_vs_fmt PRIVATE cxx_std_20) -target_link_libraries(benchmark.0019.formatting.format_vs_fmt PRIVATE fmt::fmt) diff --git a/benchmark/0019.formatting/format_vs_fmt.cc b/benchmark/0019.formatting/format_vs_fmt.cc deleted file mode 100644 index 80cfb34a7..000000000 --- a/benchmark/0019.formatting/format_vs_fmt.cc +++ /dev/null @@ -1,409 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ROUNDS 5 - -#if __has_include() -#include -#define ENABLE_STD_FORMAT_BENCH 1 -#endif - -#if __has_include() -#include -#if __has_include() -#include -#define ENABLE_FMT_BENCH 1 -#endif -#endif - -using namespace fast_io::io; -using namespace fast_io::mnp; - -struct benchmark_result -{ - std::size_t total_size{}; - fast_io::unix_timestamp elapsed{}; -}; - -template -inline benchmark_result run_bench(Func f, std::uint32_t iterations, std::uint32_t rounds = ROUNDS) -{ - // More thorough warmup - for (std::uint32_t w = 0; w < 1000; ++w) - { - auto warmup = f(w); - (void)warmup; - } - - std::vector times; - times.reserve(rounds); - std::size_t total_size{}; - - for (std::uint32_t round = 0; round < rounds; ++round) - { - auto start = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - for (std::uint32_t i{}; i != iterations; ++i) - { - auto tmp = f(i); - if (round == 0) // Only count size once - { - total_size += tmp.size(); - } - } - auto end = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - times.push_back(end - start); - } - - // Return the best (fastest) time - auto best_time = *std::min_element(times.begin(), times.end()); - return {total_size, best_time}; -} - - -inline ::fast_io::string make_record_fastio(std::uint32_t i) -{ - std::uint32_t id = i * 2654435761u + 0x9e3779b9u; - std::uint64_t val = 0xDEADBEEFCAFEBABEull ^ (std::uint64_t)id * 1315423911ull; - std::uint32_t score = (id % 10007) + 3141; - std::uint32_t rate = score / ((id % 97) + 1); - ::fast_io::string name{"fastio"}; - - // return fast_io::concat_fast_io( - // "ID=", width(scalar_placement::right, hex0xupper(id), 10, '0'), - // " VAL=", width(scalar_placement::right, hex0xupper(val), 18, '0'), - // " SCORE=", width(scalar_placement::right, strvw(score_s), 12), - // " RATE=", width(scalar_placement::right, strvw(rate_s), 10), - // " NAME=", left(strvw(name), 16, '.')); - - return fast_io::concat_fast_io( - "ID=", width(scalar_placement::right, hex0xupper(id), 10, '0'), - " VAL=", width(scalar_placement::right, hex0xupper(val), 18, '0'), - " SCORE=", width(scalar_placement::right, score, 12), - " RATE=", width(scalar_placement::right, rate, 10), - " NAME=", left(name, 16, '.')); -} - -#if defined(ENABLE_STD_FORMAT_BENCH) -inline std::string make_record_stdformat(std::uint32_t i) -{ - std::uint32_t id = i * 2654435761u + 0x9e3779b9u; - std::uint64_t val = 0xDEADBEEFCAFEBABEull ^ static_cast(id) * 1315423911ull; - std::uint32_t score = (id % 10007) + 3141; - std::uint32_t rate = score / ((id % 97) + 1); - constexpr auto name = "fastio"; - return std::format("ID={:#010X} VAL={:#018X} SCORE={:>12} RATE={:>10} NAME={:.<16}", - id, val, score, rate, name); -} -#endif - -#if __has_include() && defined(ENABLE_FMT_BENCH) -inline std::string make_record_fmt(std::uint32_t i) -{ - std::uint32_t id = i * 2654435761u + 0x9e3779b9u; - std::uint64_t val = 0xDEADBEEFCAFEBABEull ^ static_cast(id) * 1315423911ull; - std::uint32_t score = (id % 10007) + 3141; - std::uint32_t rate = score / ((id % 97) + 1); - constexpr auto name = "fastio"; - -#if __has_include() - return fmt::format(FMT_COMPILE("ID={:#010X} VAL={:#018X} SCORE={:>12} RATE={:>10} NAME={:.<16}"), - id, val, score, rate, name); -#else - return fmt::format("ID={:#010X} VAL={:#018X} SCORE={:>12} RATE={:>10} NAME={:.<16}", - id, val, score, rate, name); -#endif -} -#endif - -// iostream -inline std::string make_record_iostream(std::uint32_t i) -{ - std::uint32_t id = i * 2654435761u + 0x9e3779b9u; - std::uint64_t val = 0xDEADBEEFCAFEBABEull ^ static_cast(id) * 1315423911ull; - std::uint32_t score = (id % 10007) + 3141; - std::uint32_t rate = score / ((id % 97) + 1); - constexpr char const *name = "fastio"; - - std::ostringstream oss; - oss.setf(std::ios::uppercase); - oss << "ID=" << std::showbase << std::internal << std::setfill('0') << std::setw(10) << std::hex << id; - oss << std::dec << std::setfill(' ') << std::nouppercase; // reset - oss.setf(std::ios::uppercase); - oss << " VAL=" << std::showbase << std::internal << std::setfill('0') << std::setw(18) << std::hex << val; - oss << std::dec << std::setfill(' '); - oss << " SCORE=" << std::setw(12) << std::right << score; - oss << " RATE=" << std::setw(10) << std::right << rate; - oss << " NAME=" << std::left << std::setw(16) << std::setfill('.') << name; - return oss.str(); -} - -// -------- write benchmark (buffered/no buffered) to /dev/null, avoid disk interference -------- -inline benchmark_result run_write_bench_fastio(std::uint32_t iterations, bool buffered_128k) -{ - std::size_t total_size{}; - auto start = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - if (buffered_128k) - { - fast_io::native_file nf("/dev/null", fast_io::open_mode::out | fast_io::open_mode::trunc); - std::string buf; - buf.reserve(128 * 1024); - for (std::uint32_t i{}; i != iterations; ++i) - { - auto rec = make_record_fastio(i); - total_size += rec.size(); - if (buf.size() + rec.size() + 1 > 128 * 1024) - { - if (!buf.empty()) - { - ::fast_io::operations::write_all(nf, buf.data(), buf.data() + buf.size()); - buf.clear(); - } - if (rec.size() + 1 > 128 * 1024) - { - ::fast_io::operations::write_all(nf, rec.data(), rec.data() + rec.size()); - char nl = '\n'; - ::fast_io::operations::write_all(nf, &nl, &nl + 1); - continue; - } - } - buf.append(rec.data(), rec.size()); - buf.push_back('\n'); - } - if (!buf.empty()) - { - ::fast_io::operations::write_all(nf, buf.data(), buf.data() + buf.size()); - } - } - else - { - fast_io::native_file nf("/dev/null", fast_io::open_mode::out | fast_io::open_mode::trunc); - for (std::uint32_t i{}; i != iterations; ++i) - { - auto rec = make_record_fastio(i); - total_size += rec.size(); - ::fast_io::operations::write_all(nf, rec.data(), rec.data() + rec.size()); - char nl = '\n'; - ::fast_io::operations::write_all(nf, &nl, &nl + 1); - } - } - auto end = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - return {total_size, end - start}; -} - -inline benchmark_result run_write_bench_fmt(std::uint32_t iterations, bool buffered_128k) -{ - std::size_t total_size{}; - auto start = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - if (buffered_128k) - { - fast_io::native_file nf("/dev/null", fast_io::open_mode::out | fast_io::open_mode::trunc); - std::string buf; - buf.reserve(128 * 1024); - for (std::uint32_t i{}; i != iterations; ++i) - { - auto rec = make_record_fmt(i); - total_size += rec.size(); - if (buf.size() + rec.size() + 1 > 128 * 1024) - { - if (!buf.empty()) - { - ::fast_io::operations::write_all(nf, buf.data(), buf.data() + buf.size()); - buf.clear(); - } - if (rec.size() + 1 > 128 * 1024) - { - ::fast_io::operations::write_all(nf, rec.data(), rec.data() + rec.size()); - char nl = '\n'; - ::fast_io::operations::write_all(nf, &nl, &nl + 1); - continue; - } - } - buf.append(rec.data(), rec.size()); - buf.push_back('\n'); - } - if (!buf.empty()) - { - ::fast_io::operations::write_all(nf, buf.data(), buf.data() + buf.size()); - } - } - else - { - fast_io::native_file nf("/dev/null", fast_io::open_mode::out | fast_io::open_mode::trunc); - for (std::uint32_t i{}; i != iterations; ++i) - { - auto rec = make_record_fmt(i); - total_size += rec.size(); - print(nf, rec, '\n'); - } - } - auto end = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - return {total_size, end - start}; -} - -inline benchmark_result run_write_bench_iostream(std::uint32_t iterations, bool buffered_128k) -{ - std::size_t total_size{}; - auto start = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - { - std::ofstream out("/dev/null", std::ios::binary | std::ios::trunc); - std::vector bigbuf; - if (buffered_128k) - { - bigbuf.resize(128 * 1024); - out.rdbuf()->pubsetbuf(bigbuf.data(), static_cast(bigbuf.size())); - } - else - { - out.rdbuf()->pubsetbuf(nullptr, 0); - } - for (std::uint32_t i{}; i != iterations; ++i) - { - auto rec = make_record_iostream(i); - total_size += rec.size(); - out.write(rec.data(), static_cast(rec.size())); - out.put('\n'); - } - out.flush(); - } - auto end = fast_io::posix_clock_gettime(fast_io::posix_clock_id::monotonic_raw); - return {total_size, end - start}; -} - -int main(int argc, char **argv) -{ - std::uint32_t iterations = 10000000; - std::uint32_t rounds = ROUNDS; - if (argc >= 2) - { - try - { - iterations = ::fast_io::to(::fast_io::mnp::os_c_str(argv[1])); - } - catch (...) - { - // ignore invalid input, keep default - } - } - if (argc >= 3) - { - try - { - rounds = ::fast_io::to(::fast_io::mnp::os_c_str(argv[2])); - } - catch (...) - { - // ignore invalid input, keep default ROUNDS - } - } - - auto sample_fastio = make_record_fastio(1); -#if defined(ENABLE_STD_FORMAT_BENCH) - auto sample_stdformat = make_record_stdformat(1); -#endif -#if __has_include() && defined(ENABLE_FMT_BENCH) - auto sample_fmt = make_record_fmt(1); -#endif - - using namespace fast_io::io; - print("Sample fast_io output: ", sample_fastio, "\n"); -#if defined(ENABLE_STD_FORMAT_BENCH) - print("Sample std::format output:", sample_stdformat, "\n"); -#endif -#if __has_include() && defined(ENABLE_FMT_BENCH) - print("Sample fmt output: ", sample_fmt, "\n"); -#endif - print("\n"); - - print("Running benchmarks with ", iterations, " iterations, ", rounds, " rounds each (showing best time)...\n\n"); - - auto fastio_res = run_bench(make_record_fastio, iterations, rounds); - print("fast_io completed\n"); - -#if defined(ENABLE_STD_FORMAT_BENCH) - auto stdformat_res = run_bench(make_record_stdformat, iterations, rounds); - print("std::format completed\n"); -#endif - -#if __has_include() && defined(ENABLE_FMT_BENCH) - auto fmt_res = run_bench(make_record_fmt, iterations, rounds); - print("fmt completed\n"); -#endif - - print("\n[format benchmark results]\n"); - print("Iterations: ", iterations, ", Rounds: ", rounds, "\n"); - print("fast_io (total size: ", fastio_res.total_size, ") took ", fastio_res.elapsed, "s\n"); - -#if defined(ENABLE_STD_FORMAT_BENCH) - print("std::format (total size: ", stdformat_res.total_size, ") took ", stdformat_res.elapsed, "s"); - // Calculate speedup - constexpr double subseconds_to_seconds = 1.0 / static_cast(fast_io::uint_least64_subseconds_per_second); - double stdformat_seconds = static_cast(stdformat_res.elapsed.seconds) + - static_cast(stdformat_res.elapsed.subseconds) * subseconds_to_seconds; - double fastio_seconds = static_cast(fastio_res.elapsed.seconds) + - static_cast(fastio_res.elapsed.subseconds) * subseconds_to_seconds; - if (fastio_seconds > 0) - { - double speedup = stdformat_seconds / fastio_seconds; - std::string speedup_str = std::format("{:.2f}", speedup); - print(" (fast_io is ", speedup_str, "x faster)\n"); - } - else - { - print("\n"); - } -#endif - -#if __has_include() && defined(ENABLE_FMT_BENCH) - print("fmt (total size: ", fmt_res.total_size, ") took ", fmt_res.elapsed, "s"); - constexpr double subseconds_to_seconds2 = 1.0 / static_cast(fast_io::uint_least64_subseconds_per_second); - double fmt_seconds = static_cast(fmt_res.elapsed.seconds) + - static_cast(fmt_res.elapsed.subseconds) * subseconds_to_seconds2; - double fastio_seconds2 = static_cast(fastio_res.elapsed.seconds) + - static_cast(fastio_res.elapsed.subseconds) * subseconds_to_seconds2; - if (fastio_seconds2 > 0) - { - double fmt_speedup = fmt_seconds / fastio_seconds2; - std::string fmt_speedup_str = std::format("{:.2f}", fmt_speedup); - print(" (fast_io is ", fmt_speedup_str, "x faster)\n"); - } - else - { - print("\n"); - } -#endif - - print("\n\n[write benchmark to /dev/null]\n"); - // fast_io write: 128KB buffered vs direct system call - { - auto fio_buf = run_write_bench_fastio(iterations, true); - auto fio_nobuf = run_write_bench_fastio(iterations, false); - print("fast_io obuf(128K) (size: ", fio_buf.total_size, ") took ", fio_buf.elapsed, "s\n"); - print("fast_io native(no) (size: ", fio_nobuf.total_size, ") took ", fio_nobuf.elapsed, "s\n"); - } - // iostream write: 128KB buffered vs no buffered - // { - // auto iostream_buf = run_write_bench_iostream(iterations, true); - // auto iostream_nobuf = run_write_bench_iostream(iterations, false); - // print("iostream 128K buf (size: ", iostream_buf.total_size, ") took ", iostream_buf.elapsed, "s\n"); - // print("iostream no buf (size: ", iostream_nobuf.total_size, ") took ", iostream_nobuf.elapsed, "s\n"); - // } -#if __has_include() && defined(ENABLE_FMT_BENCH) - // fmt write: 128KB buffered vs direct system call (format with FMT_COMPILE) - { - auto fmt_buf = run_write_bench_fmt(iterations, true); - auto fmt_nobuf = run_write_bench_fmt(iterations, false); - print("fmt(FMT_COMPILE)+buf (size: ", fmt_buf.total_size, ") took ", fmt_buf.elapsed, "s\n"); - print("fmt(FMT_COMPILE)+no (size: ", fmt_nobuf.total_size, ") took ", fmt_nobuf.elapsed, "s\n"); - } -#endif -} diff --git a/benchmark/0021.io/file_vs_stdio.cc b/benchmark/0019.io/file_vs_stdio.cc similarity index 100% rename from benchmark/0021.io/file_vs_stdio.cc rename to benchmark/0019.io/file_vs_stdio.cc diff --git a/benchmark/0020.teju_vs_dragonbox/CMakeLists.txt b/benchmark/0020.teju_vs_dragonbox/CMakeLists.txt deleted file mode 100644 index 6b72cd4b7..000000000 --- a/benchmark/0020.teju_vs_dragonbox/CMakeLists.txt +++ /dev/null @@ -1,79 +0,0 @@ -include(FetchContent) - -# JKJ Dragonbox -FetchContent_Declare( - dragonbox - GIT_REPOSITORY https://github.com/jk-jeon/dragonbox -) - -# Teju Jaguá (for to_decimal API) -FetchContent_Declare( - teju_jagua - GIT_REPOSITORY https://github.com/cassioneri/teju_jagua - GIT_TAG main -) - -# Disable testing in all subprojects to avoid "test" target conflicts -set(BUILD_TESTING OFF CACHE BOOL "" FORCE) - -# Fetch dragonbox first (no issues) -FetchContent_MakeAvailable(dragonbox) - -# For teju_jagua, use a pre-configure hook to patch the conflicting target name -function(patch_teju_test_target) - FetchContent_GetProperties(teju_jagua) - if(NOT teju_jagua_POPULATED) - FetchContent_Populate(teju_jagua) - - # Patch the test target name to avoid conflict with CTest - file(READ ${teju_jagua_SOURCE_DIR}/cpp/test/CMakeLists.txt TEJU_TEST_CMAKE_CONTENT) - string(REPLACE "add_executable(test" "add_executable(teju_test" TEJU_TEST_CMAKE_CONTENT_PATCHED "${TEJU_TEST_CMAKE_CONTENT}") - string(REPLACE "target_include_directories(test" "target_include_directories(teju_test" TEJU_TEST_CMAKE_CONTENT_PATCHED "${TEJU_TEST_CMAKE_CONTENT_PATCHED}") - string(REPLACE "target_link_libraries(test" "target_link_libraries(teju_test" TEJU_TEST_CMAKE_CONTENT_PATCHED "${TEJU_TEST_CMAKE_CONTENT_PATCHED}") - file(WRITE ${teju_jagua_SOURCE_DIR}/cpp/test/CMakeLists.txt "${TEJU_TEST_CMAKE_CONTENT_PATCHED}") - endif() - - # Now add the subdirectory with the patched content - add_subdirectory(${teju_jagua_SOURCE_DIR} ${teju_jagua_BINARY_DIR} EXCLUDE_FROM_ALL) -endfunction() - -# Apply the patch and build teju_jagua -patch_teju_test_target() - -# Fix teju_jagua's include paths for both teju and common targets -# The issue is that teju_jagua's CMakeLists.txt uses ${CMAKE_SOURCE_DIR} which points to fast_io root in our context -# But teju_jagua expects it to point to teju_jagua root -if(TARGET teju) - # Override the problematic include directory for teju C library - set_target_properties(teju PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "") - target_include_directories(teju PUBLIC - ${teju_jagua_SOURCE_DIR}/teju/include - ) - target_include_directories(teju PRIVATE - ${teju_jagua_SOURCE_DIR} - ) -endif() - -if(TARGET common) - # Override the problematic include directory for cpp/common - set_target_properties(common PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "") - target_include_directories(common PUBLIC - ${teju_jagua_SOURCE_DIR}/cpp/common/include - ) - target_include_directories(common PRIVATE - ${teju_jagua_SOURCE_DIR} - ) -endif() - -add_executable(benchmark.0020.teju_vs_dragonbox.teju_vs_dragonbox ${CMAKE_CURRENT_LIST_DIR}/teju_vs_dragonbox.cc) - -target_include_directories(benchmark.0020.teju_vs_dragonbox.teju_vs_dragonbox PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${teju_jagua_SOURCE_DIR} - ${teju_jagua_SOURCE_DIR}/teju/include - ${teju_jagua_SOURCE_DIR}/cpp/common/include - ${teju_jagua_SOURCE_DIR}/third-party/dragonbox/include) - -target_link_libraries(benchmark.0020.teju_vs_dragonbox.teju_vs_dragonbox PRIVATE - dragonbox::dragonbox_to_chars - teju) diff --git a/benchmark/0020.teju_vs_dragonbox/teju_vs_dragonbox.cc b/benchmark/0020.teju_vs_dragonbox/teju_vs_dragonbox.cc deleted file mode 100644 index 33f0c3047..000000000 --- a/benchmark/0020.teju_vs_dragonbox/teju_vs_dragonbox.cc +++ /dev/null @@ -1,162 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace fast_io::io; - -template -static std::vector make_random_values(std::size_t n) -{ - std::mt19937_64 eng{123456789u}; - std::uniform_real_distribution dist(std::numeric_limits::denorm_min(), std::numeric_limits::max()); - std::vector v; - v.reserve(n); - for (std::size_t i = 0; i < n; ++i) - { - v.push_back(dist(eng)); // strictly positive finite by construction - } - return v; -} - -static void bench_float(std::size_t n) -{ - auto values = make_random_values(n); - - if(!values.empty()) - { - char fio_buf[128]; - char dbx_buf[128]; - char teju_buf[128]; - auto const x0 = values.front(); - char* fio_p = fast_io::pr_rsv_to_c_array(fio_buf, fast_io::mnp::scientific(x0)); - char* dbx_p = jkj::dragonbox::to_chars(x0, dbx_buf); - char* teju_p = jkj::dragonbox::to_chars(x0, teju_buf); - fast_io::println( - "sample fast_io=", fast_io::mnp::strvw(fio_buf, fio_p), - " dragonbox=", fast_io::mnp::strvw(dbx_buf, dbx_p), - " teju=", fast_io::mnp::strvw(teju_buf, teju_p)); - } - - { - fast_io::timer t(u8"fastio_float"); - std::uint64_t acc{}; - for (auto const x : values) - { - // auto const [mantissa, exponent, sign] = fast_io::details::get_punned_result(x); - // (void)sign; - // auto const r = fast_io::details::dragonbox_impl(mantissa, static_cast<::std::int_least32_t>(exponent)); - // acc += static_cast(r.m10) + static_cast(r.e10); - char buf[128]; - auto *p = fast_io::pr_rsv_to_c_array(buf, fast_io::mnp::scientific(x)); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } - - { - using namespace jkj::dragonbox; - fast_io::timer t(u8"dragonbox_float"); - std::uint64_t acc{}; - for (auto const x : values) - { - char buf[128]{0}; - auto *p = to_chars(x, buf); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } - - { - fast_io::timer t(u8"teju_float"); - std::uint64_t acc{}; - for (auto const x : values) - { - char buf[128]{0}; - auto *p = jkj::dragonbox::to_chars(x, buf); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } -} - -static void bench_double(std::size_t n) -{ - auto values = make_random_values(n); - - if(!values.empty()) - { - char fio_buf[128]; - char dbx_buf[128]; - char teju_buf[128]; - auto const x0 = values.front(); - char* fio_p = fast_io::pr_rsv_to_c_array(fio_buf, fast_io::mnp::scientific(x0)); - char* dbx_p = jkj::dragonbox::to_chars(x0, dbx_buf); - char* teju_p = jkj::dragonbox::to_chars(x0, teju_buf); - fast_io::println( - "sample fast_io=", fast_io::mnp::strvw(fio_buf, fio_p), - " dragonbox=", fast_io::mnp::strvw(dbx_buf, dbx_p), - " teju=", fast_io::mnp::strvw(teju_buf, teju_p)); - } - - { - // fast_io core (dragonbox_impl only, no string assembly) - fast_io::timer t(u8"fastio_double"); - std::uint64_t acc{}; - for (auto const x : values) - { - char buf[128]{0}; - auto *p = fast_io::pr_rsv_to_c_array(buf, fast_io::mnp::scientific(x)); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } - - { - using namespace jkj::dragonbox; - fast_io::timer t(u8"dragonbox_double"); - std::uint64_t acc{}; - for (auto const x : values) - { - char buf[128]{0}; - auto *p = to_chars(x, buf); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } - - { - fast_io::timer t(u8"teju_double"); - std::uint64_t acc{}; - for (auto const x : values) - { - char buf[128]{0}; - auto *p = jkj::dragonbox::to_chars(x, buf); - acc += static_cast(p - buf); - } - std::uint64_t volatile sink = acc; - (void)sink; - } -} - -int main() -{ - constexpr std::size_t N = 1u << 20; // ~1M samples - bench_float(N); - print("\n"); - bench_double(N); -} diff --git a/benchmark/0021.io/CMakeLists.txt b/benchmark/0021.io/CMakeLists.txt deleted file mode 100644 index 1d00b849d..000000000 --- a/benchmark/0021.io/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_executable(benchmark.0021.io.file_vs_stdio ${CMAKE_CURRENT_LIST_DIR}/file_vs_stdio.cc) -target_include_directories(benchmark.0021.io.file_vs_stdio PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_compile_features(benchmark.0021.io.file_vs_stdio PRIVATE cxx_std_20) diff --git a/benchmark/0022.from_chars/CMakeLists.txt b/benchmark/0022.from_chars/CMakeLists.txt deleted file mode 100644 index 9b3cb0371..000000000 --- a/benchmark/0022.from_chars/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -include(FetchContent) - -FetchContent_Declare( - fast_float - GIT_REPOSITORY https://github.com/fastfloat/fast_float - GIT_TAG main -) -FetchContent_MakeAvailable(fast_float) - - -add_executable(benchmark.0022.from_chars ${CMAKE_CURRENT_LIST_DIR}/atoi_vs_from_chars.cc) -target_include_directories(benchmark.0022.from_chars PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_compile_features(benchmark.0022.from_chars PRIVATE cxx_std_20) - -# Prefer official target if provided by fast_float; otherwise include headers directly -if (TARGET fast_float::fast_float) - target_link_libraries(benchmark.0022.from_chars PRIVATE fast_float::fast_float) -else() - FetchContent_GetProperties(fast_float) - if (fast_float_SOURCE_DIR) - target_include_directories(benchmark.0022.from_chars PRIVATE ${fast_float_SOURCE_DIR}/include) - endif() -endif() - -add_executable(benchmark.0022.from_chars_hex ${CMAKE_CURRENT_LIST_DIR}/atoi_vs_from_chars_hex.cc) -target_include_directories(benchmark.0022.from_chars_hex PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_compile_features(benchmark.0022.from_chars_hex PRIVATE cxx_std_20) - -if (TARGET fast_float::fast_float) - target_link_libraries(benchmark.0022.from_chars_hex PRIVATE fast_float::fast_float) -else() - FetchContent_GetProperties(fast_float) - if (fast_float_SOURCE_DIR) - target_include_directories(benchmark.0022.from_chars_hex PRIVATE ${fast_float_SOURCE_DIR}/include) - endif() -endif() diff --git a/benchmark/0022.from_chars/atoi_vs_from_chars.cc b/benchmark/0022.from_chars/atoi_vs_from_chars.cc deleted file mode 100644 index 419248db7..000000000 --- a/benchmark/0022.from_chars/atoi_vs_from_chars.cc +++ /dev/null @@ -1,430 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace fast_io::io; - -// NOTE: -// This benchmark compares the core integer parsing routines under identical preconditions. -// For each line, the pointer `p` is positioned at the first decimal digit; there is no -// leading whitespace, sign character, or base prefix in the [p, end) slice. -// The fast_io branch calls -// scan_int_contiguous_none_simd_space_part_define_impl<10, char>(p, end, v); -// the std::from_chars and fast_float::from_chars integer overloads are invoked on the same -// [p, end) range. -// By specification, std::from_chars and fast_float::from_chars for integer types do not -// skip leading whitespace, and scan_int_contiguous_none_simd_space_part_define_impl makes -// the same assumption that any preceding whitespace has already been consumed. Thus the -// starting conditions and termination rules are fully aligned, providing a fair comparison -// of "decimal digit substring → uint64_t" parsing performance. - -static std::string make_numbers_buffer(std::size_t n) -{ - std::string s; - s.reserve(n * 8); - for (std::size_t i{}; i != n; ++i) - { - auto old = s.size(); - s.resize(old + 32); - auto *first = s.data() + old; - auto *last = s.data() + s.size(); - auto res = std::to_chars(first, last - 1, i); - *res.ptr = '\n'; - s.resize(res.ptr - s.data() + 1); - } - return s; -} - -static std::string make_fixed_digits_numbers_buffer(std::size_t digits, std::size_t n) -{ - constexpr std::uint64_t pow10[20]{ - 1ull, - 10ull, - 100ull, - 1000ull, - 10000ull, - 100000ull, - 1000000ull, - 10000000ull, - 100000000ull, - 1000000000ull, - 10000000000ull, - 100000000000ull, - 1000000000000ull, - 10000000000000ull, - 100000000000000ull, - 1000000000000000ull, - 10000000000000000ull, - 100000000000000000ull, - 1000000000000000000ull, - 10000000000000000000ull}; - - if (digits == 0 || digits > 20) - { - return {}; - } - - std::string s; - s.reserve(n * (digits + 1)); - - std::uint64_t lo{}; - std::uint64_t count{}; - - if (digits == 1) - { - lo = 0; - count = 10; - } - else if (digits < 20) - { - lo = pow10[digits - 1]; - count = pow10[digits] - lo; - } - else - { - lo = pow10[19]; - count = (std::numeric_limits::max)() - lo + 1; - } - - for (std::size_t i{}; i != n; ++i) - { - auto old = s.size(); - s.resize(old + 32); - auto *first = s.data() + old; - auto *last = s.data() + s.size(); - std::uint64_t value = lo + static_cast(i % count); - auto res = std::to_chars(first, last - 1, value); - *res.ptr = '\n'; - s.resize(static_cast(res.ptr - s.data() + 1)); - } - return s; -} - -int main() -{ - constexpr std::size_t N = 10'000'000; - auto buf = make_numbers_buffer(N); - char const *begin = buf.data(); - char const *end = buf.data() + buf.size(); - - { - std::size_t lines{}; - for (char const *p = begin; p < end; ++p) - { - lines += (*p == '\n'); - } - fast_io::perrln("lines=", lines); - } - - // atoi - { - fast_io::timer t(u8"atoi"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - int v = std::atoi(p); - sum += static_cast(v); - while (p < end && *p >= '0' && *p <= '9') - { - ++p; - } - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // std::from_chars - { - fast_io::timer t(u8"std_from_chars"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = std::from_chars(p, end, v); - sum += v; - p = res.ptr; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - - // fast_io core sto (dec) - scalar/SWAR path: - // scan_int_contiguous_none_simd_space_part_define_impl (no SSE4.1 fast path) - { - fast_io::timer t(u8"fastio_scan_int_none_simd_dec"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_simd_space_part_define_impl<10, char>( - p, end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - -#if defined(__SSE4_1__) && (defined(__x86_64__) || defined(_M_AMD64)) - // fast_io core sto (dec) - SSE4.1-accelerated path: - // scan_int_contiguous_none_space_part_define_impl (may use sse_parse for base-10) - { - fast_io::timer t(u8"fastio_scan_int_sse4_dec"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_space_part_define_impl<10>(p, end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } -#endif - - // fast_io char_digit_to_literal (hex) - { - fast_io::timer t(u8"fastio_char_digit_to_literal"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - using UCh = std::make_unsigned_t; - std::uint64_t v{}; - char const *q = p; - while (q < end && *q != '\n') - { - UCh ch = static_cast(*q); - if (fast_io::details::char_digit_to_literal<10, char>(ch)) - { - break; - } - v = v * 10 + static_cast(ch); - ++q; - } - sum += v; - p = (q < end ? q + 1 : q); - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - - // fast_float - { - fast_io::timer t(u8"fast_float_from_chars"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = fast_float::from_chars(p, end, v); - sum += v; - p = res.ptr; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // Per-digit decimal benchmarks: 1-digit up to theoretical max decimal digits of uint64_t (20) - { - constexpr std::size_t max_digits = 20; - for (std::size_t digits = 1; digits <= max_digits; ++digits) - { - auto buf_fixed = make_fixed_digits_numbers_buffer(digits, N); - char const *fixed_begin = buf_fixed.data(); - char const *fixed_end = buf_fixed.data() + buf_fixed.size(); - - fast_io::perrln("\n\nfixed_digits=", digits, " lines=", N); - - { - std::size_t lines{}; - for (char const *p = fixed_begin; p < fixed_end; ++p) - { - lines += (*p == '\n'); - } - fast_io::perrln("lines=", lines); - } - - // atoi on fixed-width decimal substrings - { - fast_io::timer t(u8"atoi_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - int v = std::atoi(p); - sum += static_cast(v); - while (p < fixed_end && *p >= '0' && *p <= '9') - { - ++p; - } - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // std::from_chars on fixed-width decimal substrings - { - fast_io::timer t(u8"std_from_chars_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = std::from_chars(p, fixed_end, v); - sum += v; - p = res.ptr; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io char_digit_to_literal on fixed-width decimal substrings - { - fast_io::timer t(u8"fastio_char_digit_to_literal_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - using UCh = std::make_unsigned_t; - std::uint64_t v{}; - char const *q = p; - while (q < fixed_end && *q != '\n') - { - UCh ch = static_cast(*q); - if (fast_io::details::char_digit_to_literal<10, char>(ch)) - { - break; - } - v = v * 10 + static_cast(ch); - ++q; - } - sum += v; - p = (q < fixed_end ? q + 1 : q); - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io core sto (dec) - scalar/SWAR path on fixed-width decimal substrings - { - fast_io::timer t(u8"fastio_scan_int_none_simd_dec_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_simd_space_part_define_impl<10, char>( - p, fixed_end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - -#if defined(__SSE4_1__) && (defined(__x86_64__) || defined(_M_AMD64)) - // fast_io core sto (dec) - SSE4.1-accelerated path on fixed-width decimal substrings - { - fast_io::timer t(u8"fastio_scan_int_sse4_dec_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_space_part_define_impl<10>(p, fixed_end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } -#endif - - // fast_float integer from_chars on fixed-width decimal substrings - { - fast_io::timer t(u8"fast_float_from_chars_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = fast_float::from_chars(p, fixed_end, v); - sum += v; - p = res.ptr; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - } - } -} diff --git a/benchmark/0022.from_chars/atoi_vs_from_chars_hex.cc b/benchmark/0022.from_chars/atoi_vs_from_chars_hex.cc deleted file mode 100644 index 4ad5b03cf..000000000 --- a/benchmark/0022.from_chars/atoi_vs_from_chars_hex.cc +++ /dev/null @@ -1,392 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace fast_io::io; - -// NOTE: -// This benchmark compares core integer parsing in base 16 under aligned conditions. -// For each line, the pointer `p` is positioned at the first hexadecimal digit; there is no -// leading whitespace or base prefix in the [p, end) slice, and the 0–9/A–F/a–f pattern of -// each line is identical for all libraries. Newline characters '\n' are skipped manually. -// The fast_io branch calls -// scan_int_contiguous_none_simd_space_part_define_impl<16, char>(p, end, v); -// the std::from_chars and fast_float::from_chars integer overloads are invoked on the same -// [p, end) range. -// As with the decimal benchmark, std::from_chars and fast_float::from_chars for integers -// do not skip leading whitespace, and scan_int_contiguous_none_simd_space_part_define_impl -// assumes that any preceding whitespace has already been consumed. The preconditions are -// therefore identical, making this a fair comparison of "hex digit substring → uint64_t" -// parsing performance. - -static std::string make_hex_numbers_buffer(std::size_t n) -{ - std::string s; - s.reserve(n * 8); - for (std::size_t i{}; i != n; ++i) - { - auto old = s.size(); - s.resize(old + 32); - auto *first = s.data() + old; - auto *last = s.data() + s.size(); - auto res = std::to_chars(first, last - 1, i, 16); - // mix lowercase/uppercase hex digits in the buffer - if ((i & 1u) != 0u) - { - for (auto p = first; p != res.ptr; ++p) - { - if (*p >= 'a' && *p <= 'f') - { - *p = static_cast(*p - 'a' + 'A'); - } - } - } - *res.ptr = '\n'; - s.resize(static_cast(res.ptr - s.data() + 1)); - } - return s; -} - -static std::string make_fixed_hex_numbers_buffer(std::size_t digits, std::size_t n) -{ - if (digits == 0 || digits > 16) - { - return {}; - } - - constexpr std::uint64_t pow16[16]{ - 1ull, - 16ull, - 256ull, - 4096ull, - 65536ull, - 1048576ull, - 16777216ull, - 268435456ull, - 4294967296ull, - 68719476736ull, - 1099511627776ull, - 17592186044416ull, - 281474976710656ull, - 4503599627370496ull, - 72057594037927936ull, - 1152921504606846976ull}; - - std::string s; - s.reserve(n * (digits + 1)); - - std::uint64_t lo{}; - std::uint64_t hi{}; - - if (digits == 1) - { - lo = 0; - hi = 0xFu; - } - else if (digits < 16) - { - lo = pow16[digits - 1]; - hi = pow16[digits] - 1; - } - else - { - lo = pow16[15]; - hi = (std::numeric_limits::max)(); - } - - std::uint64_t count = hi - lo + 1; - - for (std::size_t i{}; i != n; ++i) - { - auto old = s.size(); - s.resize(old + 32); - auto *first = s.data() + old; - auto *last = s.data() + s.size(); - std::uint64_t value = lo + static_cast(i % count); - auto res = std::to_chars(first, last - 1, value, 16); - // mix lowercase/uppercase hex digits in the buffer - if ((value & 1u) != 0u) - { - for (auto p = first; p != res.ptr; ++p) - { - if (*p >= 'a' && *p <= 'f') - { - *p = static_cast(*p - 'a' + 'A'); - } - } - } - *res.ptr = '\n'; - s.resize(static_cast(res.ptr - s.data() + 1)); - } - return s; -} - -int main() -{ - constexpr std::size_t N = 10'000'000; - auto buf = make_hex_numbers_buffer(N); - char const *begin = buf.data(); - char const *end = buf.data() + buf.size(); - - { - std::size_t lines{}; - for (char const *p = begin; p < end; ++p) - { - lines += (*p == '\n'); - } - fast_io::perrln("lines=", lines); - } - - // strtoul (hex) - { - fast_io::timer t(u8"strtoul_hex"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - char *endptr{}; - auto v = std::strtoul(p, &endptr, 16); - sum += static_cast(v); - p = endptr; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // std::from_chars (hex) - { - fast_io::timer t(u8"std_from_chars_hex"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = std::from_chars(p, end, v, 16); - sum += v; - p = res.ptr; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io core sto (hex) - scan_int_contiguous_none_simd_space_part_define_impl - { - fast_io::timer t(u8"fastio_scan_int_none_simd_hex (SIMT)"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_simd_space_part_define_impl<16, char>( - p, end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io char_digit_to_literal (hex) - { - fast_io::timer t(u8"fastio_char_digit_to_literal_hex"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - using UCh = std::make_unsigned_t; - std::uint64_t v{}; - char const *q = p; - while (q < end && *q != '\n') - { - UCh ch = static_cast(*q); - if (fast_io::details::char_digit_to_literal<16, char>(ch)) - { - break; - } - v = (v << 4) + static_cast(ch); - ++q; - } - sum += v; - p = (q < end ? q + 1 : q); - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_float (hex) - { - fast_io::timer t(u8"fast_float_from_chars_hex"); - std::uint64_t sum{}; - char const *p = begin; - while (p < end) - { - std::uint64_t v{}; - auto res = fast_float::from_chars(p, end, v, 16); - sum += v; - p = res.ptr; - if (p < end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // Per-digit hex benchmarks: 1-digit up to theoretical max hexadecimal digits of uint64_t (16) - { - constexpr std::size_t max_hex_digits = 16; - for (std::size_t digits = 1; digits <= max_hex_digits; ++digits) - { - auto buf_fixed = make_fixed_hex_numbers_buffer(digits, N); - char const *fixed_begin = buf_fixed.data(); - char const *fixed_end = buf_fixed.data() + buf_fixed.size(); - - fast_io::perrln("\n\nfixed_hex_digits=", digits, " lines=", N); - - { - std::size_t lines{}; - for (char const *p = fixed_begin; p < fixed_end; ++p) - { - lines += (*p == '\n'); - } - fast_io::perrln("lines=", lines); - } - - // strtoul (hex) on fixed-width hex substrings - { - fast_io::timer t(u8"strtoul_hex_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - char *endptr{}; - auto v = std::strtoul(p, &endptr, 16); - sum += static_cast(v); - p = endptr; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // std::from_chars (hex) on fixed-width hex substrings - { - fast_io::timer t(u8"std_from_chars_hex_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = std::from_chars(p, fixed_end, v, 16); - sum += v; - p = res.ptr; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io char_digit_to_literal (hex) on fixed-width hex substrings - { - fast_io::timer t(u8"fastio_char_digit_to_literal_hex_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - using UCh = std::make_unsigned_t; - std::uint64_t v{}; - char const *q = p; - while (q < fixed_end && *q != '\n') - { - UCh ch = static_cast(*q); - if (fast_io::details::char_digit_to_literal<16, char>(ch)) - { - break; - } - v = (v << 4) + static_cast(ch); - ++q; - } - sum += v; - p = (q < fixed_end ? q + 1 : q); - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_io core sto (hex) on fixed-width hex substrings - { - fast_io::timer t(u8"fastio_scan_int_none_simd_hex_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = ::fast_io::details::scan_int_contiguous_none_simd_space_part_define_impl<16, char>( - p, fixed_end, v); - if (res.code != fast_io::parse_code::ok) - { - break; - } - sum += v; - p = res.iter; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - - // fast_float (hex) on fixed-width hex substrings - { - fast_io::timer t(u8"fast_float_from_chars_hex_fixed"); - std::uint64_t sum{}; - char const *p = fixed_begin; - while (p < fixed_end) - { - std::uint64_t v{}; - auto res = fast_float::from_chars(p, fixed_end, v, 16); - sum += v; - p = res.ptr; - if (p < fixed_end && *p == '\n') - { - ++p; - } - } - std::uint64_t volatile sink = sum; - (void)sink; - } - } - } -}