finish adding json output
authorJacob Lifshay <programmerjake@gmail.com>
Wed, 27 Jul 2022 07:07:56 +0000 (00:07 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Wed, 27 Jul 2022 07:07:56 +0000 (00:07 -0700)
src/harness.cpp
src/harness.h
src/json.h
src/main.cpp

index e28eac94ea70ca44cee9febcbb3fc76293ec0a7e..e6fbda3f3128e49e4d9f9e350bb9353734d9480a 100644 (file)
@@ -1,4 +1,5 @@
 #include "harness.h"
+#include "json.h"
 #include <atomic>
 #include <chrono>
 #include <cmath>
@@ -11,6 +12,7 @@
 #include <shared_mutex>
 #include <thread>
 #include <variant>
+#include <vector>
 
 #ifdef NDEBUG // assert needs to work even in release mode
 #undef NDEBUG
@@ -171,8 +173,68 @@ struct WriteDuration final
     }
 };
 
-void BenchHarnessBase::base_run(
-    const Config &config,
+struct BenchmarkResultInner final
+{
+    struct Result final
+    {
+        std::chrono::duration<double> total_dur;
+        std::chrono::duration<double> iter_dur;
+        operator JsonValue() const
+        {
+            return JsonValue::Object{
+                {"total_dur", total_dur.count()},
+                {"iter_dur", iter_dur.count()},
+            };
+        }
+    };
+    std::string name;
+    JsonValue config_json;
+    std::uint64_t iteration_count;
+    Result average;
+    std::vector<Result> threads;
+};
+
+struct BenchmarkResultImpl final : public BenchmarkResult
+{
+    BenchmarkResultInner inner;
+    BenchmarkResultImpl(BenchmarkResultInner inner) : inner(std::move(inner))
+    {
+    }
+    virtual void print() const override
+    {
+        std::cout << inner.name << ":\n";
+        if (inner.threads.size() > 1)
+        {
+            for (std::size_t i = 0; i < inner.threads.size(); i++)
+            {
+                std::cout << "Thread #" << i << " took "
+                          << WriteDuration{inner.threads[i].total_dur}
+                          << " for " << inner.iteration_count
+                          << " iterations -- "
+                          << WriteDuration{inner.threads[i].iter_dur}
+                          << "/iter.\n";
+            }
+        }
+        std::cout << "Average elapsed time: "
+                  << WriteDuration{inner.average.total_dur} << " for "
+                  << inner.iteration_count << " iterations -- "
+                  << WriteDuration{inner.average.iter_dur} << "/iter.\n"
+                  << std::endl;
+    }
+    virtual operator JsonValue() const override
+    {
+        return JsonValue::Object{
+            {"name", inner.name},
+            {"config", inner.config_json},
+            {"iteration_count", inner.iteration_count},
+            {"average", inner.average},
+            {"threads", inner.threads},
+        };
+    }
+};
+
+std::shared_ptr<BenchmarkResult> BenchHarnessBase::base_run(
+    const Config &config, const std::string &name,
     void (*fn)(BenchHarnessBase *bench_harness_base,
                std::uint64_t iteration_count, std::uint32_t thread_num))
 {
@@ -214,6 +276,7 @@ void BenchHarnessBase::base_run(
     if (config.iteration_count)
     {
         iteration_count = *config.iteration_count;
+        assert(iteration_count > 0);
         run(iteration_count);
     }
     else
@@ -227,7 +290,8 @@ void BenchHarnessBase::base_run(
                 total_elapsed += i;
             }
             auto target_average_elapsed = std::chrono::milliseconds(500);
-            if (total_elapsed > thread_count * target_average_elapsed)
+            if (total_elapsed > thread_count * target_average_elapsed ||
+                iteration_count >= (1ULL << 63))
             {
                 break;
             }
@@ -235,25 +299,28 @@ void BenchHarnessBase::base_run(
         }
     }
     steady_clock::duration total_elapsed{};
+    BenchmarkResultInner retval = {
+        .name = name,
+        .config_json = config,
+        .iteration_count = iteration_count,
+        .average = {},
+        .threads = {},
+    };
     for (std::uint32_t thread_num = 0; thread_num < thread_count; thread_num++)
     {
         total_elapsed += elapsed[thread_num];
-        if (thread_count > 1)
-        {
-            auto dur = std::chrono::duration<double>(elapsed[thread_num]);
-            std::cout << "Thread #" << thread_num << " took "
-                      << WriteDuration{dur} << " for " << iteration_count
-                      << " iterations -- "
-                      << WriteDuration{dur / iteration_count} << "/iter.\n";
-        }
+        auto dur = std::chrono::duration<double>(elapsed[thread_num]);
+        retval.threads.push_back({
+            .total_dur = dur,
+            .iter_dur = dur / iteration_count,
+        });
     }
     auto total = std::chrono::duration<double>(total_elapsed);
-    std::cout << "Average elapsed time: "
-              << WriteDuration{total / thread_count} << " for "
-              << iteration_count << " iterations -- "
-              << WriteDuration{total / thread_count / iteration_count}
-              << "/iter.\n"
-              << std::endl;
+    retval.average = {
+        .total_dur = total / thread_count,
+        .iter_dur = total / thread_count / iteration_count,
+    };
+    return std::make_shared<BenchmarkResultImpl>(retval);
 }
 
 std::shared_ptr<void> BenchHarnessBase::get_thread_cache()
index 72ee8d1c36458141018030b95cdbc37270517e30..b9ab39d38709d1ae217f317e009040c781ab1696 100644 (file)
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "json.h"
 #include <cstdint>
 #include <functional>
 #include <memory>
@@ -18,10 +19,28 @@ struct Config final
     std::uint32_t log2_stride = 0;
     static constexpr std::uint32_t max_sum_log2_mem_loc_count_and_stride = 28;
     bool use_json = false;
+    operator JsonValue() const
+    {
+        return JsonValue::Object{
+            {"thread_count", thread_count},
+            {"iteration_count", iteration_count},
+            {"log2_memory_location_count", log2_memory_location_count},
+            {"log2_stride", log2_stride},
+            {"use_json", use_json},
+        };
+    }
 };
 
 template <typename Fn, typename Input> class BenchHarness;
 
+struct BenchmarkResult
+{
+    BenchmarkResult() = default;
+    virtual ~BenchmarkResult() = default;
+    virtual void print() const = 0;
+    virtual operator JsonValue() const = 0;
+};
+
 class BenchHarnessBase
 {
     template <typename Fn, typename Input> friend class BenchHarness;
@@ -30,10 +49,10 @@ class BenchHarnessBase
     std::shared_ptr<void> thread_cache;
     class ThreadCache;
     friend class ThreadCache;
-    void base_run(const Config &config,
-                  void (*fn)(BenchHarnessBase *bench_harness_base,
-                             std::uint64_t iteration_count,
-                             std::uint32_t thread_num));
+    std::shared_ptr<BenchmarkResult> base_run(
+        const Config &config, const std::string &name,
+        void (*fn)(BenchHarnessBase *bench_harness_base,
+                   std::uint64_t iteration_count, std::uint32_t thread_num));
 
   public:
     static std::shared_ptr<void> get_thread_cache();
@@ -51,36 +70,38 @@ class BenchHarness final : private BenchHarnessBase
         : fn(std::move(fn)), input(std::move(input))
     {
     }
-    void run(const Config &config)
+    std::shared_ptr<BenchmarkResult> run(const Config &config,
+                                         const std::string &name)
     {
-        base_run(config, [](BenchHarnessBase *bench_harness_base,
-                            std::uint64_t iteration_count,
-                            std::uint32_t thread_num) {
-            auto self = static_cast<BenchHarness *>(bench_harness_base);
-            auto &fn = self->fn;
-            // copy for repeatability, also so optimization barrier is on copy,
-            // not self
-            auto input = self->input;
-            for (std::uint64_t i = 0; i < iteration_count; i++)
-            {
-                // optimization barrier
-                asm("" : : "r"(std::addressof(input)) : "memory");
-
-                if constexpr (std::is_void_v<std::invoke_result_t<
-                                  Fn &, Input, decltype(i),
-                                  decltype(thread_num)>>)
+        return base_run(
+            config, name,
+            [](BenchHarnessBase *bench_harness_base,
+               std::uint64_t iteration_count, std::uint32_t thread_num) {
+                auto self = static_cast<BenchHarness *>(bench_harness_base);
+                auto &fn = self->fn;
+                // copy for repeatability, also so optimization barrier is on
+                // copy, not self
+                auto input = self->input;
+                for (std::uint64_t i = 0; i < iteration_count; i++)
                 {
-                    fn(input, i, thread_num);
-                }
-                else
-                {
-                    auto output = fn(input, i, thread_num);
-
                     // optimization barrier
-                    asm("" : : "r"(std::addressof(output)) : "memory");
+                    asm("" : : "r"(std::addressof(input)) : "memory");
+
+                    if constexpr (std::is_void_v<std::invoke_result_t<
+                                      Fn &, Input, decltype(i),
+                                      decltype(thread_num)>>)
+                    {
+                        fn(input, i, thread_num);
+                    }
+                    else
+                    {
+                        auto output = fn(input, i, thread_num);
+
+                        // optimization barrier
+                        asm("" : : "r"(std::addressof(output)) : "memory");
+                    }
                 }
-            }
-        });
+            });
     }
 };
 
@@ -88,19 +109,23 @@ class Benchmark final
 {
   private:
     std::string m_name;
-    std::function<void(const Config &config)> m_run;
+    std::function<std::shared_ptr<BenchmarkResult>(const Config &config,
+                                                   const std::string &name)>
+        m_run;
 
   public:
     template <typename Fn, typename Input>
     explicit Benchmark(Fn fn, Input input, std::string name)
-        : m_name(std::move(name)), m_run([fn, input](const Config &config) {
-              return BenchHarness(std::move(fn), std::move(input)).run(config);
+        : m_name(std::move(name)),
+          m_run([fn, input](const Config &config, const std::string &name) {
+              return BenchHarness(std::move(fn), std::move(input))
+                  .run(config, name);
           })
     {
     }
-    void run(const Config &config)
+    std::shared_ptr<BenchmarkResult> run(const Config &config) const
     {
-        return m_run(config);
+        return m_run(config, m_name);
     }
     const std::string &name() const
     {
index 63abd3a0a881073fdc3512c0e0e15c30e264e8b4..5aee708c739588c58fd6982f62639149092d8d5f 100644 (file)
 #pragma once
 
+#include <cassert>
 #include <charconv>
 #include <cmath>
 #include <cstddef>
 #include <cstdint>
 #include <cstdio>
 #include <cstdlib>
+#include <exception>
+#include <functional>
 #include <initializer_list>
 #include <ios>
+#include <iterator>
 #include <memory>
 #include <optional>
 #include <ostream>
 #include <string>
 #include <string_view>
 #include <system_error>
+#include <type_traits>
 #include <unordered_map>
 #include <variant>
 #include <vector>
 
-struct JsonValue;
+enum class ResolveResult : bool
+{
+    Finished = false,
+    MoreToResolve = true,
+};
+
+template <typename T> class LazyVec final
+{
+  private:
+    struct Internals
+    {
+        std::vector<T> resolved_values;
+        explicit Internals(std::vector<T> &&resolved_values)
+            : resolved_values(std::move(resolved_values))
+        {
+        }
+        explicit Internals(std::initializer_list<T> resolved_values = {})
+            : resolved_values(resolved_values)
+        {
+        }
+        virtual ~Internals() = default;
+        virtual ResolveResult resolve_more()
+        {
+            return ResolveResult::Finished;
+        }
+    };
+    template <
+        typename Iterator, typename Sentinel,
+        typename = std::enable_if_t<
+            std::is_base_of_v<
+                std::input_iterator_tag,
+                typename std::iterator_traits<Iterator>::iterator_category> &&
+            std::is_same_v<
+                const typename std::iterator_traits<Iterator>::value_type,
+                const T>>>
+    struct LazyInternalsIter final : public Internals
+    {
+        struct State final
+        {
+            Iterator cur;
+            Sentinel end;
+            State(Iterator &&cur, Sentinel &&end) noexcept
+                : cur(std::move(cur)), end(std::move(end))
+            {
+            }
+        };
+        std::optional<State> state;
+        LazyInternalsIter(Iterator &&cur, Sentinel &&end) noexcept
+            : state(std::in_place, std::move(cur), std::move(end))
+        {
+            if (state->cur == state->end)
+            {
+                state = std::nullopt;
+            }
+        }
+        LazyInternalsIter(const LazyInternalsIter &) = delete;
+        LazyInternalsIter &operator=(const LazyInternalsIter &) = delete;
+        virtual ResolveResult resolve_more() override
+        {
+            if (!state)
+            {
+                return ResolveResult::Finished;
+            }
+            this->resolved_values.emplace_back(*state->cur++);
+            if (state->cur == state->end)
+            {
+                state = std::nullopt;
+                return ResolveResult::Finished;
+            }
+            return ResolveResult::MoreToResolve;
+        }
+    };
+    template <
+        typename Container,
+        typename Iterator = decltype(std::begin(std::declval<Container>())),
+        typename Sentinel = decltype(std::end(std::declval<Container>())),
+        typename = decltype(LazyInternalsIter<Iterator, Sentinel>::cur)>
+    struct LazyInternalsContainer final : public Internals
+    {
+        struct State final
+        {
+            Container container;
+            Iterator cur; // must come after container
+            Sentinel end; // must come after container
+            explicit State(Container &&container) noexcept
+                : container(std::move(container)),
+                  cur(std::begin(this->container)),
+                  end(std::begin(this->container))
+            {
+            }
+            State(const State &) = delete;
+            State &operator=(const State &) = delete;
+        };
+        std::optional<State> state;
+        explicit LazyInternalsContainer(Container &&container) noexcept
+            : state(std::in_place, std::move(container))
+        {
+            if (state->cur == state->end)
+            {
+                state = std::nullopt;
+            }
+        }
+        virtual ResolveResult resolve_more() override
+        {
+            if (!state)
+            {
+                return ResolveResult::Finished;
+            }
+            this->resolved_values.emplace_back(*state->cur++);
+            if (state->cur == state->end)
+            {
+                state = std::nullopt;
+                return ResolveResult::Finished;
+            }
+            return ResolveResult::MoreToResolve;
+        }
+    };
+    template <typename Fn,
+              typename = std::enable_if_t<std::is_convertible_v<
+                  decltype(std::declval<Fn &>()()), std::optional<T>>>>
+    struct LazyInternalsFn final : public Internals
+    {
+        std::optional<Fn> fn;
+        explicit LazyInternalsFn(Fn &&fn) noexcept
+            : fn(std::in_place, std::move(fn))
+        {
+        }
+        virtual ResolveResult resolve_more() override
+        {
+            if (!fn)
+            {
+                return ResolveResult::Finished;
+            }
+            if (std::optional<T> value = (*fn)())
+            {
+                this->resolved_values.emplace_back(std::move(*value));
+                return ResolveResult::MoreToResolve;
+            }
+            fn = std::nullopt;
+            return ResolveResult::Finished;
+        }
+    };
+    std::shared_ptr<Internals> internals;
+
+  public:
+    LazyVec() noexcept : internals(std::make_shared<Internals>())
+    {
+    }
+    LazyVec(std::vector<T> values) noexcept
+        : internals(std::make_shared<Internals>(std::move(values)))
+    {
+    }
+    LazyVec(std::initializer_list<T> values) noexcept
+        : internals(std::make_shared<Internals>(values))
+    {
+    }
+    template <typename Iterator, typename Sentinel,
+              typename = decltype(LazyInternalsIter<Iterator, Sentinel>::cur)>
+    LazyVec(Iterator begin, Sentinel end) noexcept
+        : internals(std::make_shared<LazyInternalsIter<Iterator, Sentinel>>(
+              std::move(begin), std::move(end)))
+    {
+    }
+    template <typename Container, typename = decltype(LazyInternalsContainer<
+                                                      Container>::container)>
+    LazyVec(Container container) noexcept
+        : internals(std::make_shared<LazyInternalsContainer<Container>>(
+              std::move(container)))
+    {
+    }
+    template <typename Fn, typename = void,
+              typename = decltype(LazyInternalsFn<Fn>::fn)>
+    LazyVec(Fn fn) noexcept
+        : internals(std::make_shared<LazyInternalsFn<Fn>>(std::move(fn)))
+    {
+    }
+
+  private:
+    static bool resolve_at(const std::shared_ptr<Internals> &internals,
+                           std::size_t index) noexcept
+    {
+        while (index >= internals->resolved_values.size())
+        {
+            switch (internals->resolve_more())
+            {
+            case ResolveResult::Finished:
+                goto end;
+            case ResolveResult::MoreToResolve:
+                continue;
+            }
+        }
+    end:
+        return index < internals->resolved_values.size();
+    }
+
+  public:
+    bool resolve_at(std::size_t index) noexcept
+    {
+        return resolve_at(internals, index);
+    }
+    const T &at(std::size_t index) noexcept
+    {
+        if (!resolve_at(index))
+        {
+            assert(!"index out of bounds");
+            std::terminate();
+        }
+        return internals->resolved_values[index];
+    }
+    const T &operator[](std::size_t index) noexcept
+    {
+        return at(index);
+    }
+    std::size_t size_lower_bound() noexcept
+    {
+        return internals->resolved_values.size();
+    }
+    ResolveResult resolve_more()
+    {
+        return internals->resolve_more();
+    }
+    const std::vector<T> &resolve()
+    {
+        while (true)
+        {
+            switch (resolve_more())
+            {
+            case ResolveResult::Finished:
+                return internals->resolved_values;
+            case ResolveResult::MoreToResolve:
+                continue;
+            }
+        }
+    }
+    class const_iterator final
+    {
+        friend class LazyVec;
+
+      private:
+        std::shared_ptr<Internals> internals;
+        std::size_t index = 0;
+        bool resolve() const noexcept
+        {
+            assert(internals);
+            if (!internals)
+            {
+                std::terminate();
+            }
+            return resolve_at(internals, index);
+        }
+        explicit const_iterator(
+            const std::shared_ptr<Internals> &internals) noexcept
+            : internals(internals)
+        {
+        }
+
+      public:
+        using difference_type = std::ptrdiff_t;
+        using value_type = T;
+        using pointer = const T *;
+        using reference = const T &;
+        using iterator_category = std::forward_iterator_tag;
+
+        const_iterator() noexcept = default;
+        const_iterator &operator++()
+        {
+            assert(internals);
+            ++index;
+            return *this;
+        }
+        const_iterator operator++(int)
+        {
+            auto retval = *this;
+            operator++();
+            return retval;
+        }
+        pointer operator->() const noexcept
+        {
+            if (at_end())
+            {
+                assert(!"tried to dereference an end() iterator");
+                std::terminate();
+            }
+            return std::addressof(internals->resolved_values[index]);
+        }
+        reference operator*() const noexcept
+        {
+            return *operator->();
+        }
+        bool at_end() const noexcept
+        {
+            return !resolve();
+        }
+        bool operator==(const const_iterator &rhs) const noexcept
+        {
+            if (rhs.internals)
+            {
+                if (internals)
+                {
+                    assert(internals == rhs.internals);
+                    return index == rhs.index;
+                }
+                return rhs.at_end();
+            }
+            if (internals)
+            {
+                return at_end();
+            }
+            return true;
+        }
+        bool operator!=(const const_iterator &rhs) const noexcept
+        {
+            return !operator==(rhs);
+        }
+    };
+    const_iterator begin() const noexcept
+    {
+        return const_iterator(internals);
+    }
+    const_iterator cbegin() const noexcept
+    {
+        return const_iterator(internals);
+    }
+    const_iterator end() const noexcept
+    {
+        return const_iterator();
+    }
+    const_iterator cend() const noexcept
+    {
+        return const_iterator();
+    }
+    template <typename R, typename Fn> LazyVec<R> filter_map(Fn &&fn) noexcept
+    {
+        struct FilterMapper final
+        {
+            const_iterator iter;
+            Fn fn;
+            std::optional<R> operator()() noexcept
+            {
+                while (iter != const_iterator())
+                {
+                    if (std::optional<R> retval = fn(*iter++))
+                    {
+                        return retval;
+                    }
+                }
+                return std::nullopt;
+            }
+        };
+        return LazyVec<R>(FilterMapper{.iter = begin(), .fn = std::move(fn)});
+    }
+    template <typename R, typename Fn> LazyVec<R> map(Fn fn) noexcept
+    {
+        return filter_map<R>(
+            [fn](const T &value) -> std::optional<R> { return fn(value); });
+    }
+    template <typename Fn> LazyVec filter(Fn fn) noexcept
+    {
+        return filter_map<T>([fn](const T &value) -> std::optional<T> {
+            if (fn(value))
+                return value;
+            return std::nullopt;
+        });
+    }
+};
 
-using JsonString = std::string;
-using JsonFloat = double;
-using JsonNull = std::nullptr_t;
-using JsonArray = std::vector<JsonValue>;
-using JsonMap = std::vector<std::pair<std::string, JsonValue>>;
+struct JsonValue;
 
 struct JsonValue final
 {
-    std::variant<JsonString, JsonFloat, JsonNull, std::unique_ptr<JsonArray>,
-                 std::unique_ptr<JsonMap>>
-        value;
+    using String = std::string;
+    using Number = double;
+    using Object = LazyVec<std::pair<String, JsonValue>>;
+    using Array = LazyVec<JsonValue>;
+    using Bool = bool;
+    using Null = std::nullptr_t;
+    std::variant<String, Number, Object, Array, Bool, Null> value;
     constexpr JsonValue() noexcept : value(nullptr)
     {
     }
-    constexpr JsonValue(JsonNull) noexcept : value(nullptr)
+    JsonValue(String value) noexcept : value(std::move(value))
+    {
+    }
+    JsonValue(const char *value) noexcept : value(std::string(value))
     {
     }
-    constexpr JsonValue(JsonFloat value) noexcept : value(value)
+    JsonValue(std::string_view value) noexcept : value(std::string(value))
     {
     }
-    JsonValue(JsonString value) noexcept : value(std::move(value))
+    constexpr JsonValue(Number value) noexcept : value(value)
     {
     }
-    JsonValue(std::unique_ptr<JsonArray> value) noexcept
-        : value(std::move(value))
+    JsonValue(Object value) noexcept : value(std::move(value))
     {
     }
-    JsonValue(JsonArray value) noexcept
-        : value(std::make_unique<JsonArray>(std::move(value)))
+    JsonValue(Array value) noexcept : value(std::move(value))
     {
     }
-    JsonValue(std::unique_ptr<JsonMap> value) noexcept
-        : value(std::move(value))
+    constexpr JsonValue(Bool value) noexcept : value(value)
     {
     }
-    JsonValue(JsonMap value) noexcept
-        : value(std::make_unique<JsonMap>(std::move(value)))
+    constexpr JsonValue(Null) noexcept : value(nullptr)
+    {
+    }
+    constexpr JsonValue(char) noexcept = delete;
+    constexpr JsonValue(char16_t) noexcept = delete;
+    constexpr JsonValue(char32_t) noexcept = delete;
+#define JSON_VALUE_NUM(T)                                                     \
+    constexpr JsonValue(T value) noexcept                                     \
+        : JsonValue(static_cast<double>(value))                               \
+    {                                                                         \
+    }
+    JSON_VALUE_NUM(float)
+    JSON_VALUE_NUM(long double)
+    JSON_VALUE_NUM(unsigned char)
+    JSON_VALUE_NUM(signed char)
+    JSON_VALUE_NUM(unsigned short)
+    JSON_VALUE_NUM(short)
+    JSON_VALUE_NUM(unsigned int)
+    JSON_VALUE_NUM(int)
+    JSON_VALUE_NUM(unsigned long)
+    JSON_VALUE_NUM(long)
+    JSON_VALUE_NUM(unsigned long long)
+    JSON_VALUE_NUM(long long)
+#undef JSON_VALUE_NUM
+    template <typename T, typename = decltype(JsonValue(std::declval<T &&>()))>
+    constexpr JsonValue(std::optional<T> value) noexcept : value(nullptr)
+    {
+        if (value)
+        {
+            *this = JsonValue(std::move(*value));
+        }
+    }
+    template <typename T,
+              typename = decltype(JsonValue(std::declval<const T &>()))>
+    JsonValue(LazyVec<T> value) noexcept
+        : value(value.template map<JsonValue>(
+              [](const T &value) { return JsonValue(value); }))
+    {
+    }
+    template <typename T,
+              typename = decltype(JsonValue(std::declval<const T &>()))>
+    JsonValue(std::vector<T> value) noexcept
+        : JsonValue(LazyVec<T>(std::move(value)))
     {
     }
     /// decode a JsonString from WTF-8 to the encoding logically used in JSON:
@@ -198,188 +608,249 @@ struct JsonValue final
             }
         }
     };
-    template <typename WriteStringView>
-    void write(WriteStringView &&write_fn) const
+
+  private:
+    template <typename WriteStringView> struct Visitor final
     {
-        struct Visitor final
+        WriteStringView &write_fn;
+        bool pretty;
+        std::size_t indent = 0;
+        void write(std::string_view str)
         {
-            WriteStringView &write_fn;
-            void write(std::string_view str)
-            {
-                write_fn(str);
-            }
-            void write(char ch)
+            write_fn(str);
+        }
+        void write(char ch)
+        {
+            write_fn(std::string_view(&ch, 1));
+        }
+        void write_indent()
+        {
+            for (std::size_t i = 0; i < indent; i++)
             {
-                write_fn(std::string_view(&ch, 1));
+                write("    ");
             }
-            void operator()(const JsonString &value)
+        }
+        void operator()(const String &value)
+        {
+            write('\"');
+            JsonStringDecoder decoder(value);
+            while (auto value_opt = decoder.next())
             {
-                write('\"');
-                JsonStringDecoder decoder(value);
-                while (auto value_opt = decoder.next())
+                std::uint16_t value = *value_opt;
+                switch (value)
                 {
-                    std::uint16_t value = *value_opt;
-                    switch (value)
+                case '\"':
+                case '\\':
+                    write('\\');
+                    write(static_cast<char>(value));
+                    break;
+                case '\b':
+                    write("\\b");
+                    break;
+                case '\f':
+                    write("\\f");
+                    break;
+                case '\n':
+                    write("\\n");
+                    break;
+                case '\r':
+                    write("\\r");
+                    break;
+                case '\t':
+                    write("\\t");
+                    break;
+                default:
+                    if (value >= 0x20 && value <= 0x7E)
                     {
-                    case '\"':
-                    case '\\':
-                        write('\\');
                         write(static_cast<char>(value));
-                        break;
-                    case '\b':
-                        write("\\b");
-                        break;
-                    case '\f':
-                        write("\\f");
-                        break;
-                    case '\n':
-                        write("\\n");
-                        break;
-                    case '\r':
-                        write("\\r");
-                        break;
-                    case '\t':
-                        write("\\t");
-                        break;
-                    default:
-                        if (value >= 0x20 && value <= 0x7E)
-                        {
-                            write(static_cast<char>(value));
-                        }
-                        else
+                    }
+                    else
+                    {
+                        static constexpr char hex_digits[] =
+                            "0123456789ABCDEF";
+                        write("\\u");
+                        for (int i = 0; i < 4; i++)
                         {
-                            static constexpr char hex_digits[] =
-                                "0123456789ABCDEF";
-                            write("\\u");
-                            for (int i = 0; i < 4; i++)
-                            {
-                                write(hex_digits[value >> 12]);
-                                value <<= 4;
-                            }
+                            write(hex_digits[value >> 12]);
+                            value <<= 4;
                         }
-                        break;
                     }
+                    break;
                 }
-                write('"');
             }
-            void operator()(JsonFloat value)
+            write('"');
+        }
+        void operator()(Number value)
+        {
+            if (std::isnan(value))
+            {
+                write("NaN");
+                return;
+            }
+            if (std::signbit(value))
             {
-                if (std::isnan(value))
+                write('-');
+                value = -value;
+            }
+            if (std::isinf(value))
+            {
+                write("Infinity");
+                return;
+            }
+            if (value == 0)
+            {
+                write("0");
+                return;
+            }
+            using Buf = std::array<char, 32>;
+            auto try_format = [&](Buf &buf, bool e_format, int prec) -> bool {
+                int result;
+                if (e_format)
                 {
-                    write("NaN");
-                    return;
+                    result = std::snprintf(&buf[0], buf.size(), "%1.*e", prec,
+                                           value);
                 }
-                if (std::signbit(value))
+                else
                 {
-                    write('-');
-                    value = -value;
+                    result = std::snprintf(&buf[0], buf.size(), "%1.*f", prec,
+                                           value);
                 }
-                if (std::isinf(value))
+                if (result <= 0)
+                    return false;
+                double parsed_value = std::strtod(&buf[0], nullptr);
+                if (parsed_value != value)
                 {
-                    write("Infinity");
-                    return;
+                    // not precise enough
+                    return false;
                 }
-                if (value == 0)
+                return true;
+            };
+            Buf final_buf = {};
+            std::optional<std::string_view> final;
+            std::size_t end_prec = final_buf.size();
+            for (std::size_t prec = 0;
+                 prec < final_buf.size() && prec < end_prec; prec++)
+            {
+                Buf buf;
+                if (try_format(buf, true, prec))
                 {
-                    write("0");
-                    return;
-                }
-                using Buf = std::array<char, 32>;
-                auto try_format = [&](Buf &buf, bool e_format,
-                                      int prec) -> bool {
-                    int result;
-                    if (e_format)
+                    std::string_view str(&buf[0]);
+                    if (!final || str.size() < final->size())
                     {
-                        result = std::snprintf(&buf[0], buf.size(), "%1.*e",
-                                               prec, value);
+                        final_buf = buf;
+                        final = std::string_view(&final_buf[0], str.size());
+                        end_prec = prec + 3;
                     }
-                    else
-                    {
-                        result = std::snprintf(&buf[0], buf.size(), "%1.*f",
-                                               prec, value);
-                    }
-                    if (result <= 0)
-                        return false;
-                    double parsed_value = std::strtod(&buf[0], nullptr);
-                    if (parsed_value != value)
-                    {
-                        // not precise enough
-                        return false;
-                    }
-                    return true;
-                };
-                Buf final_buf = {};
-                std::optional<std::string_view> final;
-                std::size_t end_prec = final_buf.size();
-                for (std::size_t prec = 0;
-                     prec < final_buf.size() && prec < end_prec; prec++)
+                }
+                if (try_format(buf, false, prec))
                 {
-                    Buf buf;
-                    if (try_format(buf, true, prec))
+                    std::string_view str(&buf[0]);
+                    if (!final || str.size() < final->size())
                     {
-                        std::string_view str(&buf[0]);
-                        if (!final || str.size() < final->size())
-                        {
-                            final_buf = buf;
-                            final =
-                                std::string_view(&final_buf[0], str.size());
-                            end_prec = prec + 3;
-                        }
-                    }
-                    if (try_format(buf, false, prec))
-                    {
-                        std::string_view str(&buf[0]);
-                        if (!final || str.size() < final->size())
-                        {
-                            final_buf = buf;
-                            final =
-                                std::string_view(&final_buf[0], str.size());
-                            end_prec = prec + 3;
-                        }
+                        final_buf = buf;
+                        final = std::string_view(&final_buf[0], str.size());
+                        end_prec = prec + 3;
                     }
                 }
-                if (final_buf[0] == '.')
-                {
-                    write('0');
-                }
-                write(*final);
             }
-            void operator()(JsonNull)
+            if (final_buf[0] == '.')
             {
-                write("null");
+                write('0');
             }
-            void operator()(const std::unique_ptr<JsonArray> &value)
+            write(*final);
+        }
+        void operator()(Bool value)
+        {
+            write(value ? "true" : "false");
+        }
+        void operator()(Null)
+        {
+            write("null");
+        }
+        template <typename T, typename Fn>
+        void write_container_inner(const LazyVec<T> &value, Fn fn)
+        {
+            indent++;
+            std::string_view sep{};
+            bool any = false;
+            for (auto &v : value)
             {
-                write('[');
-                std::string_view sep{};
-                for (auto &i : *value)
+                any = true;
+                write(sep);
+                if (pretty)
                 {
-                    write(sep);
-                    sep = ",";
-                    std::visit(*this, i.value);
+                    write('\n');
+                    write_indent();
                 }
-                write(']');
+                sep = ",";
+                fn(v);
             }
-            void operator()(const std::unique_ptr<JsonMap> &value)
+            indent--;
+            if (pretty && any)
             {
-                write('{');
-                std::string_view sep{};
-                for (auto &[k, v] : *value)
-                {
-                    write(sep);
-                    sep = ",";
-                    operator()(k);
-                    write(':');
-                    std::visit(*this, v.value);
-                }
-                write('}');
+                write('\n');
+                write_indent();
             }
-        };
-        std::visit(Visitor{.write_fn = write_fn}, value);
+        }
+        void operator()(const Array &value)
+        {
+            write('[');
+            write_container_inner(value, [&](const JsonValue &value) {
+                std::visit(*this, value.value);
+            });
+            write(']');
+        }
+        void operator()(const Object &value)
+        {
+            write('{');
+            write_container_inner(
+                value, [&](const std::pair<String, JsonValue> &value) {
+                    operator()(value.first);
+                    write(':');
+                    if (pretty)
+                    {
+                        write(' ');
+                    }
+                    std::visit(*this, value.second.value);
+                });
+            write('}');
+        }
+    };
+
+  public:
+    template <typename WriteStringView>
+    void write(WriteStringView &&write_fn, bool pretty = false) const
+    {
+        std::visit(
+            Visitor<WriteStringView>{
+                .write_fn = write_fn,
+                .pretty = pretty,
+            },
+            value);
     }
+    struct PrettyJsonValue;
+    PrettyJsonValue pretty(bool pretty = true) const noexcept;
     friend std::ostream &operator<<(std::ostream &os, const JsonValue &self)
     {
         self.write([&](std::string_view str) { os << str; });
         return os;
     }
 };
+
+struct JsonValue::PrettyJsonValue final
+{
+    JsonValue value;
+    bool pretty;
+    friend std::ostream &operator<<(std::ostream &os,
+                                    const PrettyJsonValue &self)
+    {
+        self.value.write([&](std::string_view str) { os << str; },
+                         self.pretty);
+        return os;
+    }
+};
+
+inline auto JsonValue::pretty(bool pretty) const noexcept -> PrettyJsonValue
+{
+    return {.value = *this, .pretty = pretty};
+}
\ No newline at end of file
index 4944a0d907cbbe2dc801b8ce18ef83ed436bdc26..f9bde51eea3437d63f615c18096e8a4cae070b39 100644 (file)
@@ -2,6 +2,7 @@
 #include "harness.h"
 #include "json.h"
 #include <charconv>
+#include <cstdint>
 #include <cstdlib>
 #include <functional>
 #include <initializer_list>
@@ -401,6 +402,7 @@ int main(int, char **argv)
 {
     Config config{};
     std::optional<std::unordered_set<std::string>> enabled_benchmarks;
+    bool json_pretty = false;
     Options options{
         Option{
             .short_name = 'h',
@@ -424,7 +426,8 @@ int main(int, char **argv)
                .value_kind = OptionValueKind::Required,
                .parse_value =
                    [&](OptionsParser &parser, auto value) {
-                       parser.parse_int(value, config.iteration_count);
+                       parser.parse_int(value, config.iteration_count, true,
+                                        {.min_value = 1});
                    }},
         Option{.long_name = "log2-mem-loc-count",
                .description =
@@ -468,6 +471,14 @@ int main(int, char **argv)
                .description = "Write the output in JSON format",
                .value_kind = OptionValueKind::None,
                .parse_value = [&](auto &, auto) { config.use_json = true; }},
+        Option{.long_name = "json-pretty",
+               .description = "Write the output in pretty JSON format",
+               .value_kind = OptionValueKind::None,
+               .parse_value =
+                   [&](auto &, auto) {
+                       config.use_json = true;
+                       json_pretty = true;
+                   }},
     };
     OptionsParser parser(options, argv);
     auto args = parser.parse();
@@ -475,7 +486,7 @@ int main(int, char **argv)
     {
         parser.help_and_exit("unexpected argument");
     }
-    auto benchmarks = all_benchmarks(config);
+    auto benchmarks = LazyVec(all_benchmarks(config));
     if (enabled_benchmarks)
     {
         enabled_benchmarks->erase("");
@@ -485,12 +496,12 @@ int main(int, char **argv)
         {
             if (config.use_json)
             {
-                JsonArray names;
-                for (auto &benchmark : benchmarks)
-                {
-                    names.push_back(benchmark.name());
-                }
-                std::cout << JsonValue(std::move(names)) << std::endl;
+                std::cout << JsonValue(benchmarks.map<JsonValue>(
+                                           [](const Benchmark &benchmark) {
+                                               return benchmark.name();
+                                           }))
+                                 .pretty(json_pretty)
+                          << std::endl;
             }
             else
             {
@@ -517,15 +528,34 @@ int main(int, char **argv)
         }
     }
     auto thread_cache = BenchHarnessBase::get_thread_cache();
-    for (auto &benchmark : benchmarks)
+    auto benchmark_results =
+        benchmarks.filter_map<std::shared_ptr<BenchmarkResult>>(
+            [&](const Benchmark &benchmark)
+                -> std::optional<std::shared_ptr<BenchmarkResult>> {
+                if (enabled_benchmarks &&
+                    !enabled_benchmarks->count(benchmark.name()))
+                {
+                    return std::nullopt;
+                }
+                std::cout.flush();
+                return benchmark.run(config);
+            });
+    if (config.use_json)
+    {
+        std::cout << JsonValue(benchmark_results.map<JsonValue>(
+                                   [](const std::shared_ptr<BenchmarkResult>
+                                          &benchmark_results) -> JsonValue {
+                                       return *benchmark_results;
+                                   }))
+                         .pretty(json_pretty);
+    }
+    else
     {
-        // FIXME: change output to use JSON when selected
-        if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name()))
+        for (const std::shared_ptr<BenchmarkResult> &benchmark_results :
+             benchmark_results)
         {
-            continue;
+            benchmark_results->print();
         }
-        std::cout << "Running: " << benchmark.name() << std::endl;
-        benchmark.run(config);
     }
     std::cout << std::endl;
     return 0;