diff --git a/src/tools/ConsoleBench/ConsoleBench.exe.manifest b/src/tools/ConsoleBench/ConsoleBench.exe.manifest
new file mode 100644
index 00000000000..380e2e03a37
--- /dev/null
+++ b/src/tools/ConsoleBench/ConsoleBench.exe.manifest
@@ -0,0 +1,26 @@
+
+
+
+
+ true
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj b/src/tools/ConsoleBench/ConsoleBench.vcxproj
index 1e66412cc6b..5ea84a7c56f 100644
--- a/src/tools/ConsoleBench/ConsoleBench.vcxproj
+++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj
@@ -25,6 +25,9 @@
+
+
+
false
diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
index 25a667f9907..ca5d01a8e55 100644
--- a/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
+++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
@@ -49,4 +49,9 @@
Source Files
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/src/tools/ConsoleBench/arena.cpp b/src/tools/ConsoleBench/arena.cpp
index d91986c7276..8c96c341b4c 100644
--- a/src/tools/ConsoleBench/arena.cpp
+++ b/src/tools/ConsoleBench/arena.cpp
@@ -5,7 +5,7 @@ using namespace mem;
Arena::Arena(size_t bytes)
{
- m_alloc = static_cast(THROW_IF_NULL_ALLOC(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)));
+ m_alloc = static_cast(THROW_IF_NULL_ALLOC(VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_READWRITE)));
}
Arena::~Arena()
@@ -41,8 +41,18 @@ void* Arena::_push_raw(size_t bytes, size_t alignment)
{
const auto mask = alignment - 1;
const auto pos = (m_pos + mask) & ~mask;
+ const auto pos_new = pos + bytes;
const auto ptr = m_alloc + pos;
- m_pos = pos + bytes;
+
+ if (pos_new > m_commit)
+ {
+ // Commit in 1MB chunks and pre-commit 1MiB in advance.
+ const auto commit_new = (pos_new + 0x1FFFFF) & ~0xFFFFF;
+ THROW_IF_NULL_ALLOC(VirtualAlloc(m_alloc + m_commit, commit_new - m_commit, MEM_COMMIT, PAGE_READWRITE));
+ m_commit = commit_new;
+ }
+
+ m_pos = pos_new;
return ptr;
}
@@ -76,8 +86,8 @@ ScopedArena::~ScopedArena()
static [[msvc::noinline]] std::array thread_arenas_init()
{
return {
- Arena{ 64 * 1024 * 1024 },
- Arena{ 64 * 1024 * 1024 },
+ Arena{ 1024 * 1024 * 1024 },
+ Arena{ 1024 * 1024 * 1024 },
};
}
@@ -166,7 +176,9 @@ std::wstring_view mem::format(Arena& arena, const wchar_t* fmt, va_list args)
return {};
}
+ // Make space for a terminating \0 character.
len++;
+
const auto buffer = arena.push_uninitialized(len);
len = _vsnwprintf(buffer, len, fmt, args);
diff --git a/src/tools/ConsoleBench/arena.h b/src/tools/ConsoleBench/arena.h
index 1519f03377b..92409c32921 100644
--- a/src/tools/ConsoleBench/arena.h
+++ b/src/tools/ConsoleBench/arena.h
@@ -60,6 +60,7 @@ namespace mem
void* _push_uninitialized(size_t bytes, size_t alignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__);
uint8_t* m_alloc = nullptr;
+ size_t m_commit = 0;
size_t m_pos = 0;
};
@@ -96,16 +97,32 @@ namespace mem
}
template
- std::basic_string_view repeat_string(Arena& arena, std::basic_string_view in, size_t count)
+ auto repeat(Arena& arena, const T& in, size_t count) -> decltype(auto)
{
- const auto len = count * in.size();
- const auto buf = arena.push_uninitialized(len);
-
- for (size_t i = 0; i < count; ++i)
+ if constexpr (is_std_view::value)
{
- mem::copy(buf + i * in.size(), in.data(), in.size());
+ const auto data = in.data();
+ const auto size = in.size();
+ const auto len = count * size;
+ const auto buf = arena.push_uninitialized(len);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ mem::copy(buf + i * size, data, size);
+ }
+
+ return T{ buf, len };
}
+ else
+ {
+ const auto buf = arena.push_uninitialized(count);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ memcpy(buf + i, &in, sizeof(T));
+ }
- return { buf, len };
+ return std::span{ buf, count };
+ }
}
}
diff --git a/src/tools/ConsoleBench/conhost.cpp b/src/tools/ConsoleBench/conhost.cpp
index 95a0a3ea8a9..4db5352f99b 100644
--- a/src/tools/ConsoleBench/conhost.cpp
+++ b/src/tools/ConsoleBench/conhost.cpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include "arena.h"
@@ -46,12 +47,27 @@ static void conhostCopyToStringBuffer(USHORT& length, auto& buffer, const wchar_
ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path)
{
+ const auto pathLen = wcslen(path);
+ const auto isDLL = pathLen > 4 && wcscmp(&path[pathLen - 4], L".dll") == 0;
+
const auto scratch = mem::get_scratch_arena(arena);
- const auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false);
+ auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false);
auto reference = conhostCreateHandle(server.get(), L"\\Reference", false, true);
{
- const auto cmd = format(scratch.arena, LR"("%s" --server 0x%zx)", path, server.get());
+ const auto selfPath = scratch.arena.push_uninitialized(64 * 1024);
+ GetModuleFileNameW(nullptr, selfPath, 64 * 1024);
+
+ std::wstring_view cmd;
+
+ if (isDLL)
+ {
+ cmd = format(scratch.arena, LR"("%s" host %zx "%s")", selfPath, server.get(), path);
+ }
+ else
+ {
+ cmd = format(scratch.arena, LR"("%s" --server 0x%zx)", path, server.get());
+ }
uint8_t attrListBuffer[64];
@@ -154,6 +170,22 @@ ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path)
};
}
+// A continuation of spawn_conhost().
+void check_spawn_conhost_dll(int argc, const wchar_t* argv[])
+{
+ if (argc == 4 && wcscmp(argv[1], L"host") == 0)
+ {
+ const auto serverHandle = reinterpret_cast(wcstoull(argv[2], nullptr, 16));
+ const auto path = argv[3];
+
+ using Entrypoint = NTSTATUS(NTAPI*)(HANDLE);
+ const auto h = THROW_LAST_ERROR_IF_NULL(LoadLibraryExW(path, nullptr, 0));
+ const auto f = THROW_LAST_ERROR_IF_NULL(reinterpret_cast(GetProcAddress(h, "ConsoleCreateIoThread")));
+ THROW_IF_NTSTATUS_FAILED(f(serverHandle));
+ ExitThread(S_OK);
+ }
+}
+
HANDLE get_active_connection()
{
// (Not actually) FUN FACT! The handles don't mean anything and the cake is a lie!
diff --git a/src/tools/ConsoleBench/conhost.h b/src/tools/ConsoleBench/conhost.h
index e794eca555e..719a5bb87ad 100644
--- a/src/tools/ConsoleBench/conhost.h
+++ b/src/tools/ConsoleBench/conhost.h
@@ -16,5 +16,6 @@ struct ConhostHandle
};
ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path);
+void check_spawn_conhost_dll(int argc, const wchar_t* argv[]);
HANDLE get_active_connection();
void set_active_connection(HANDLE connection);
diff --git a/src/tools/ConsoleBench/main.cpp b/src/tools/ConsoleBench/main.cpp
index 4e380474ebc..31b43e6b93d 100644
--- a/src/tools/ConsoleBench/main.cpp
+++ b/src/tools/ConsoleBench/main.cpp
@@ -7,26 +7,47 @@
#include "conhost.h"
#include "utils.h"
+#define ENABLE_TEST_OUTPUT_WRITE 1
+#define ENABLE_TEST_OUTPUT_SCROLL 1
+#define ENABLE_TEST_OUTPUT_FILL 1
+#define ENABLE_TEST_OUTPUT_READ 1
+#define ENABLE_TEST_INPUT 1
+#define ENABLE_TEST_CLIPBOARD 1
+
using Measurements = std::span;
using MeasurementsPerBenchmark = std::span;
struct BenchmarkContext
{
- HWND hwnd;
- HANDLE input;
- HANDLE output;
- int64_t time_limit;
+ bool wants_more() const;
+ void mark_beg();
+ void mark_end();
+ size_t rand();
+
+ HWND hwnd = nullptr;
+ HANDLE input = nullptr;
+ HANDLE output = nullptr;
+
mem::Arena& arena;
std::string_view utf8_4Ki;
std::string_view utf8_128Ki;
std::wstring_view utf16_4Ki;
std::wstring_view utf16_128Ki;
+ std::span attr_4Ki;
+ std::span char_4Ki;
+ std::span input_4Ki;
+
+ Measurements m_measurements;
+ size_t m_measurements_off = 0;
+ int64_t m_time = 0;
+ int64_t m_time_limit = 0;
+ size_t m_rng_state = 0;
};
struct Benchmark
{
const char* title;
- void (*exec)(const BenchmarkContext& ctx, Measurements measurements);
+ void (*exec)(BenchmarkContext& ctx);
};
struct AccumulatedResults
@@ -37,158 +58,415 @@ struct AccumulatedResults
MeasurementsPerBenchmark* measurments;
};
-constexpr int32_t perf_delta(int64_t beg, int64_t end)
-{
- return static_cast(end - beg);
-}
+static constexpr COORD s_buffer_size{ 120, 9001 };
+static constexpr COORD s_viewport_size{ 120, 30 };
-static constexpr Benchmark s_benchmarks[]{
+static constexpr Benchmark s_benchmarks[] = {
+#if ENABLE_TEST_OUTPUT_WRITE
Benchmark{
.title = "WriteConsoleA 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleA(ctx.output, ctx.utf8_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ ctx.mark_beg();
+ const auto res = WriteConsoleA(ctx.output, ctx.utf8_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
.title = "WriteConsoleW 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ ctx.mark_beg();
+ const auto res = WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
.title = "WriteConsoleA 128Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleA(ctx.output, ctx.utf8_128Ki.data(), static_cast(ctx.utf8_128Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ ctx.mark_beg();
+ const auto res = WriteConsoleA(ctx.output, ctx.utf8_128Ki.data(), static_cast(ctx.utf8_128Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleW 128Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputAttribute(ctx.output, ctx.attr_4Ki.data(), static_cast(ctx.attr_4Ki.size()), pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputCharacterW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ static constexpr COORD size{ 64, 64 };
+ static constexpr SMALL_RECT rect{ 0, 0, 63, 63 };
+
+ while (ctx.wants_more())
+ {
+ auto written = rect;
+
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputW(ctx.output, ctx.char_4Ki.data(), size, pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_OUTPUT_SCROLL
+ Benchmark{
+ .title = "ScrollConsoleScreenBufferW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ for (int i = 0; i < 10; i++)
+ {
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+ }
- if (end >= ctx.time_limit)
+ static constexpr CHAR_INFO fill{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
+ static constexpr size_t w = 64;
+ static constexpr size_t h = 64;
+
+ while (ctx.wants_more())
+ {
+ auto r = ctx.rand();
+ const auto srcLeft = (r >> 0) % (s_buffer_size.X - w);
+ const auto srcTop = (r >> 16) % (s_buffer_size.Y - h);
+
+ size_t dstLeft;
+ size_t dstTop;
+ do
{
- break;
- }
+ r = ctx.rand();
+ dstLeft = (r >> 0) % (s_buffer_size.X - w);
+ dstTop = (r >> 16) % (s_buffer_size.Y - h);
+ } while (srcLeft == dstLeft && srcTop == dstTop);
+
+ const SMALL_RECT scrollRect{
+ .Left = static_cast(srcLeft),
+ .Top = static_cast(srcTop),
+ .Right = static_cast(srcLeft + w - 1),
+ .Bottom = static_cast(srcTop + h - 1),
+ };
+ const COORD destOrigin{
+ .X = static_cast(dstLeft),
+ .Y = static_cast(dstTop),
+ };
+
+ ctx.mark_beg();
+ const auto res = ScrollConsoleScreenBufferW(ctx.output, &scrollRect, nullptr, destOrigin, &fill);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
- .title = "WriteConsoleW 128Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .title = "ScrollConsoleScreenBufferW vertical",
+ .exec = [](BenchmarkContext& ctx) {
+ for (int i = 0; i < 10; i++)
{
- const auto beg = query_perf_counter();
WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ }
- if (end >= ctx.time_limit)
+ static constexpr CHAR_INFO fill{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
+ static constexpr size_t h = (4096 + s_buffer_size.X / 2) / s_buffer_size.X;
+
+ while (ctx.wants_more())
+ {
+ auto r = ctx.rand();
+ const auto srcTop = r % (s_buffer_size.Y - h);
+
+ size_t dstTop;
+ do
{
- break;
- }
+ r = ctx.rand();
+ dstTop = r % (s_buffer_size.Y - h);
+ } while (srcTop == dstTop);
+
+ const SMALL_RECT scrollRect{
+ .Left = 0,
+ .Top = static_cast(srcTop),
+ .Right = s_buffer_size.X - 1,
+ .Bottom = static_cast(srcTop + h - 1),
+ };
+ const COORD destOrigin{
+ .X = 0,
+ .Y = static_cast(dstTop),
+ };
+
+ ctx.mark_beg();
+ const auto res = ScrollConsoleScreenBufferW(ctx.output, &scrollRect, nullptr, destOrigin, &fill);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
+#endif
+#if ENABLE_TEST_OUTPUT_FILL
Benchmark{
- .title = "Copy to clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+ .title = "FillConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF5 /* ID_CONSOLE_SELECTALL */, 0);
+ ctx.mark_beg();
+ FillConsoleOutputAttribute(ctx.output, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, 4096, pos, &written);
+ ctx.mark_end();
+ debugAssert(written == 4096);
+ }
+ },
+ },
+ Benchmark{
+ .title = "FillConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
- const auto beg = query_perf_counter();
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF0 /* ID_CONSOLE_COPY */, 0);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ FillConsoleOutputCharacterW(ctx.output, L'A', 4096, pos, &written);
+ ctx.mark_end();
+ debugAssert(written == 4096);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_OUTPUT_READ
+ Benchmark{
+ .title = "ReadConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(4096);
+ DWORD read;
- if (end >= ctx.time_limit)
- {
- break;
- }
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ ReadConsoleOutputAttribute(ctx.output, buf, 4096, pos, &read);
+ ctx.mark_end();
+ debugAssert(read == 4096);
}
},
},
Benchmark{
- .title = "Paste from clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- set_clipboard(ctx.hwnd, ctx.utf16_4Ki);
+ .title = "ReadConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(4096);
+ DWORD read;
+
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ ReadConsoleOutputCharacterW(ctx.output, buf, 4096, pos, &read);
+ ctx.mark_end();
+ debugAssert(read == 4096);
+ }
+ },
+ },
+ Benchmark{
+ .title = "ReadConsoleOutputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ static constexpr COORD size{ 64, 64 };
+ static constexpr SMALL_RECT rect{ 0, 0, 63, 63 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(size.X * size.Y);
+
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ auto read = rect;
+
+ ctx.mark_beg();
+ ReadConsoleOutputW(ctx.output, buf, size, pos, &read);
+ ctx.mark_end();
+ debugAssert(read.Right == 63 && read.Bottom == 63);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_INPUT
+ Benchmark{
+ .title = "WriteConsoleInputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ DWORD written;
+
FlushConsoleInputBuffer(ctx.input);
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ ctx.mark_beg();
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ ctx.mark_end();
+ debugAssert(written == ctx.input_4Ki.size());
FlushConsoleInputBuffer(ctx.input);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
}
},
},
Benchmark{
- .title = "ReadConsoleInputW clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- static constexpr DWORD cap = 16 * 1024;
+ .title = "ReadConsoleInputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(ctx.input_4Ki.size());
+ DWORD written, read;
+ FlushConsoleInputBuffer(ctx.input);
+
+ while (ctx.wants_more())
+ {
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ debugAssert(written == ctx.input_4Ki.size());
+
+ ctx.mark_beg();
+ ReadConsoleInputW(ctx.input, buf, static_cast(ctx.input_4Ki.size()), &read);
+ ctx.mark_end();
+ debugAssert(read == ctx.input_4Ki.size());
+ }
+ },
+ },
+ Benchmark{
+ .title = "ReadConsoleW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
const auto scratch = mem::get_scratch_arena(ctx.arena);
- const auto buf = scratch.arena.push_uninitialized(cap);
- DWORD read;
+ const auto cap = static_cast(ctx.input_4Ki.size()) * 4;
+ const auto buf = scratch.arena.push_uninitialized(cap);
+ DWORD written, read;
+
+ FlushConsoleInputBuffer(ctx.input);
+
+ while (ctx.wants_more())
+ {
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ debugAssert(written == ctx.input_4Ki.size());
+ ctx.mark_beg();
+ ReadConsoleW(ctx.input, buf, cap, &read, nullptr);
+ debugAssert(read == ctx.input_4Ki.size());
+ ctx.mark_end();
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_CLIPBOARD
+ Benchmark{
+ .title = "Clipboard copy 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF5 /* ID_CONSOLE_SELECTALL */, 0);
+
+ ctx.mark_beg();
+ SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF0 /* ID_CONSOLE_COPY */, 0);
+ ctx.mark_end();
+ }
+ },
+ },
+ Benchmark{
+ .title = "Clipboard paste 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
set_clipboard(ctx.hwnd, ctx.utf16_4Ki);
FlushConsoleInputBuffer(ctx.input);
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
+ ctx.mark_beg();
SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0);
+ ctx.mark_end();
- const auto beg = query_perf_counter();
- ReadConsoleInputW(ctx.input, buf, cap, &read);
- debugAssert(read >= 1024 && read < cap);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ FlushConsoleInputBuffer(ctx.input);
}
},
},
+#endif
};
-static constexpr size_t s_benchmarks_count = _countof(s_benchmarks);
-// Each of these strings is 128 columns.
-static constexpr std::string_view payload_utf8{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor眠い子猫はマグロ狩りの夢を見る" };
-static constexpr std::wstring_view payload_utf16{ L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor眠い子猫はマグロ狩りの夢を見る" };
+static constexpr size_t s_benchmarks_count = _countof(s_benchmarks);
+static constexpr size_t s_samples_min = 20;
+static constexpr size_t s_samples_max = 1000;
+
+// 128 characters and 124 columns.
+static constexpr std::string_view s_payload_utf8{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna alΑΒΓΔΕ" };
+// 128 characters and 128 columns.
+static constexpr std::wstring_view s_payload_utf16{ L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.ΑΒΓΔΕ" };
+
+static constexpr WORD s_payload_attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
+static constexpr CHAR_INFO s_payload_char{
+ .Char = { .UnicodeChar = L'A' },
+ .Attributes = s_payload_attr,
+};
+static constexpr INPUT_RECORD s_payload_record{
+ .EventType = KEY_EVENT,
+ .Event = {
+ .KeyEvent = {
+ .bKeyDown = TRUE,
+ .wRepeatCount = 1,
+ .wVirtualKeyCode = 'A',
+ .wVirtualScanCode = 0,
+ .uChar = 'A',
+ .dwControlKeyState = 0,
+ },
+ },
+};
static bool print_warning();
static AccumulatedResults* prepare_results(mem::Arena& arena, std::span paths);
@@ -196,6 +474,7 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
static void generate_html(mem::Arena& arena, const AccumulatedResults* results);
int wmain(int argc, const wchar_t* argv[])
+try
{
if (argc < 2)
{
@@ -203,6 +482,8 @@ int wmain(int argc, const wchar_t* argv[])
return 1;
}
+ check_spawn_conhost_dll(argc, argv);
+
const auto cp = GetConsoleCP();
const auto output_cp = GetConsoleOutputCP();
const auto restore_cp = wil::scope_exit([&]() {
@@ -230,12 +511,29 @@ int wmain(int argc, const wchar_t* argv[])
{
const auto title = results->trace_names[trace_idx];
print_format(scratch.arena, "\r\n# %.*s\r\n", title.size(), title.data());
+
+ // I found that waiting between tests fixes weird bugs when launching very old conhost versions.
+ if (trace_idx != 0)
+ {
+ Sleep(5000);
+ }
+
results->measurments[trace_idx] = run_benchmarks_for_path(scratch.arena, paths[trace_idx]);
}
generate_html(scratch.arena, results);
return 0;
}
+catch (const wil::ResultException& e)
+{
+ printf("Exception: %08x\n", e.GetErrorCode());
+ return 1;
+}
+catch (...)
+{
+ printf("Unknown exception\n");
+ return 1;
+}
static bool print_warning()
{
@@ -284,7 +582,7 @@ static AccumulatedResults* prepare_results(mem::Arena& arena, std::span(9001);
- memset(buf, '\n', 9001);
- WriteFile(ctx.output, buf, 9001, nullptr, nullptr);
+ const auto buf = scratch.arena.push_uninitialized(s_buffer_size.Y);
+ memset(buf, '\n', s_buffer_size.Y);
+ WriteFile(ctx.output, buf, s_buffer_size.Y, nullptr, nullptr);
}
static std::span run_benchmarks_for_path(mem::Arena& arena, const wchar_t* path)
@@ -364,7 +663,7 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
const auto parent_hwnd = GetConsoleWindow();
const auto freq = query_perf_freq();
- const auto handle = spawn_conhost(scratch.arena, path);
+ auto handle = spawn_conhost(scratch.arena, path);
set_active_connection(handle.connection.get());
const auto print_with_parent_connection = [&](auto&&... args) {
@@ -377,45 +676,56 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
.hwnd = GetConsoleWindow(),
.input = GetStdHandle(STD_INPUT_HANDLE),
.output = GetStdHandle(STD_OUTPUT_HANDLE),
+
.arena = scratch.arena,
- .utf8_4Ki = mem::repeat_string(scratch.arena, payload_utf8, 4 * 1024 / 128),
- .utf8_128Ki = mem::repeat_string(scratch.arena, payload_utf8, 128 * 1024 / 128),
- .utf16_4Ki = mem::repeat_string(scratch.arena, payload_utf16, 4 * 1024 / 128),
- .utf16_128Ki = mem::repeat_string(scratch.arena, payload_utf16, 128 * 1024 / 128),
+ .utf8_4Ki = mem::repeat(scratch.arena, s_payload_utf8, 4 * 1024 / s_payload_utf8.size()),
+ .utf8_128Ki = mem::repeat(scratch.arena, s_payload_utf8, 128 * 1024 / s_payload_utf8.size()),
+ .utf16_4Ki = mem::repeat(scratch.arena, s_payload_utf16, 4 * 1024 / s_payload_utf16.size()),
+ .utf16_128Ki = mem::repeat(scratch.arena, s_payload_utf16, 128 * 1024 / s_payload_utf16.size()),
+ .attr_4Ki = mem::repeat(scratch.arena, s_payload_attr, 4 * 1024),
+ .char_4Ki = mem::repeat(scratch.arena, s_payload_char, 4 * 1024),
+ .input_4Ki = mem::repeat(scratch.arena, s_payload_record, 4 * 1024),
+
+ .m_measurements = scratch.arena.push_uninitialized_span(4 * 1024 * 1024),
};
prepare_conhost(ctx, parent_hwnd);
Sleep(1000);
const auto results = arena.push_uninitialized_span(s_benchmarks_count);
- for (auto& measurements : results)
- {
- measurements = arena.push_zeroed_span(2048);
- }
for (size_t bench_idx = 0; bench_idx < s_benchmarks_count; ++bench_idx)
{
const auto& bench = s_benchmarks[bench_idx];
- auto& measurements = results[bench_idx];
print_with_parent_connection("- %s", bench.title);
- // Warmup for 0.1s.
+ // Warmup for 0.1s max.
WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr);
- ctx.time_limit = query_perf_counter() + freq / 10;
- bench.exec(ctx, measurements);
+ ctx.m_measurements_off = 0;
+ ctx.m_time_limit = query_perf_counter() + freq / 10;
+ bench.exec(ctx);
- // Actual run for 1s.
+ // Actual run for 3s max.
WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr);
- ctx.time_limit = query_perf_counter() + freq;
- bench.exec(ctx, measurements);
+ ctx.m_measurements_off = 0;
+ ctx.m_time_limit = query_perf_counter() + freq * 3;
+ bench.exec(ctx);
- // Trim off trailing 0s that resulted from the time_limit.
- size_t len = measurements.size();
- for (; len > 0 && measurements[len - 1] == 0; --len)
+ const auto measurements = arena.push_uninitialized_span(std::min(ctx.m_measurements_off, s_samples_max));
+ if (ctx.m_measurements_off <= s_samples_max)
{
+ mem::copy(measurements.data(), ctx.m_measurements.data(), ctx.m_measurements_off);
}
- measurements = measurements.subspan(0, len);
+ else
+ {
+ const auto total = ctx.m_measurements_off;
+ for (size_t i = 0; i < s_samples_max; ++i)
+ {
+ measurements[i] = ctx.m_measurements[i * total / s_samples_max];
+ }
+ }
+ results[bench_idx] = measurements;
print_with_parent_connection(", done\r\n");
}
@@ -463,7 +773,7 @@ static void generate_html(mem::Arena& arena, const AccumulatedResults* results)
-
+