Skip to content

Instantly share code, notes, and snippets.

@MBulli
Created July 24, 2021 09:20
Show Gist options
  • Save MBulli/622109e22324227a1af4af7de80834c5 to your computer and use it in GitHub Desktop.
Save MBulli/622109e22324227a1af4af7de80834c5 to your computer and use it in GitHub Desktop.
Questionable benchmark code for pipline communication with gnuplot on windows (see https://sourceforge.net/p/gnuplot/bugs/2204/)
// GnuPlotPerf.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#define NOMINMAX
#include <Windows.h>
#undef near
#undef far
#include <iostream>
#include <sstream>
#include <random>
#include <chrono>
#define BUFSIZE 4096
PROCESS_INFORMATION piProcInfo;
HANDLE hChildStdInRx; // read
HANDLE hChildStdInTx; // write
HANDLE hChildStdOutRx;
HANDLE hChildStdOutTx;
std::default_random_engine gen;
std::uniform_real_distribution<double> distribution(-200.0, 200.0);
void ThrowWin32Exception(const std::string& message)
{
DWORD dw = GetLastError();
std::stringstream what;
LPSTR errorText = NULL;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&errorText,
0, NULL);
what << message << " " << errorText;
LocalFree(errorText);
throw std::exception(what.str().c_str());
}
void startGnuPlotProcess(int version)
{
// Set the bInheritHandle flag so pipe handles are inherited.
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&hChildStdOutRx, &hChildStdOutTx, &saAttr, 0)) {
ThrowWin32Exception("Failed to create pipe for stdout.");
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(hChildStdOutRx, HANDLE_FLAG_INHERIT, 0)) {
ThrowWin32Exception("Failed to disable handle inheritance for stdout pipe.");
}
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&hChildStdInRx, &hChildStdInTx, &saAttr, 0)) {
ThrowWin32Exception("Failed to create Pipe for stdin.");
}
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(hChildStdInTx, HANDLE_FLAG_INHERIT, 0)) {
ThrowWin32Exception("Failed to disable handle inheritance for stdin pipe.");
}
// Create the child process.
STARTUPINFOA siStartInfo = {};
siStartInfo.cb = sizeof(STARTUPINFOA);
siStartInfo.hStdError = hChildStdOutTx;
siStartInfo.hStdOutput = hChildStdOutTx;
siStartInfo.hStdInput = hChildStdInRx;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// build command line
std::string cmd = "";
switch (version)
{
case 528: cmd = "D:\\Temp\\gnuplot\\528\\bin\\gnuplot.exe"; std::cout << "Gnuplot 5.2 patchlevel 8\n"; break;
case 540: cmd = "D:\\Temp\\gnuplot\\540\\bin\\gnuplot.exe"; std::cout << "Gnuplot 5.4 patchlevel 0\n"; break;
case 541: cmd = "D:\\Temp\\gnuplot\\541\\bin\\gnuplot.exe"; std::cout << "Gnuplot 5.4 patchlevel 1\n"; break;
case 542: cmd = "D:\\Temp\\gnuplot\\542\\bin\\gnuplot.exe"; std::cout << "Gnuplot 5.4 patchlevel 2\n"; break;
case 550: cmd = "D:\\Temp\\gnuplot\\550\\bin\\gnuplot.exe"; std::cout << "Version 5.5 patchlevel 0 last modified 2021-07-12\n"; break;
default:
break;
}
// Use ACSII to not to deal with std::string LPTSTR conversion
BOOL bSuccess = CreateProcessA(
cmd.c_str(), // binary
NULL, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
if (!bSuccess) {
ThrowWin32Exception("Failed to create gnuplot process.");
}
}
void sendData()
{
const int N = 500000;
std::cout << "N = " << N << std::endl;
std::stringstream data;
data << "set terminal qt" << std::endl;
data << "splot '-' with points" << std::endl;
for (size_t i = 0; i < N; i++)
{
double x = distribution(gen);
double y = distribution(gen);
double z = distribution(gen);
data << x << " " << y << " " << z << std::endl;
}
data << "e" << std::endl;
data << "unset multiplot" << std::endl;
DWORD dwRead = data.str().size();
DWORD dwWritten;
BOOL bSuccess = FALSE;
std::cout << "Writing " << dwRead << " bytes" << std::endl;
auto start = std::chrono::high_resolution_clock::now();
if (!WriteFile(hChildStdInTx, data.str().c_str(), dwRead, &dwWritten, NULL)) {
ThrowWin32Exception("Failed to write to stdin.");
}
auto finish = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = finish - start;
std::cout << "Elapsed time: " << elapsed.count() << " s\n";
std::cout << "Wrote " << dwWritten << " bytes" << std::endl;
}
void readData()
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;)
{
bSuccess = ReadFile(hChildStdOutRx, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0) break;
bSuccess = WriteFile(hParentStdOut, chBuf,
dwRead, &dwWritten, NULL);
if (!bSuccess) break;
}
}
int main()
{
auto versions = std::vector<int>{ 528, 540, 541, 542, 550 };
std::cout << "Start.\n";
startGnuPlotProcess(versions[4]);
for (int i = 0; i < 1; i++)
{
sendData();
Sleep(500);
}
std::cout << "Done.\n";
readData();
while (1)
{
}
}
@MBulli
Copy link
Author

MBulli commented Jul 24, 2021

Quick and dirty results on Intel Core i5-7600K CPU @ 3.80GHz 3.80 GHz:

Gnuplot 5.2 patchlevel 8
N = 500000
Writing 12589958 bytes
Elapsed time: 1.00804 s
Wrote 12589958 bytes
Done.


Gnuplot 5.4 patchlevel 0
N = 500000
Writing 12589958 bytes
Elapsed time: 1.00536 s
Wrote 12589958 bytes
Done.


Gnuplot 5.4 patchlevel 1
N = 500000
Writing 12589958 bytes
Elapsed time: 0.945512 s
Wrote 12589958 bytes
Done.


Gnuplot 5.4 patchlevel 2
N = 500000
Writing 12589958 bytes
Elapsed time: 0.982691 s
Wrote 12589958 bytes
Done.


Version 5.5 patchlevel 0 last modified 2021-07-12
N = 500000
Writing 12589958 bytes
Elapsed time: 1.11003 s
Wrote 12589958 bytes
Done.

@ArmoredPony
Copy link

On AMD Ryzen 7 7735HS it takes about 22 seconds (!)
Average CPU speed during tests is ~4.5 GHz
Gnuplot version 6.0 patchlevel rc1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment