前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通过edge://tracing工具进行C++的可视化基准测试

通过edge://tracing工具进行C++的可视化基准测试

原创
作者头像
晨星成焰
修改2024-04-02 18:37:44
2512
修改2024-04-02 18:37:44
举报

如何简单且持续的关注自己的函数性能表现?

这是一个通过chrono库时间函数写的Time类简单检验函数性能例子

代码语言:cpp
复制
#include <iostream>
#include <string>
#include <chrono>

class Timer {
public:
	Timer(const char* name)
		:name(name),stopped(false)
	{
		startTime = std::chrono::high_resolution_clock::now();
	}
	void Stop() {
		auto endTime = std::chrono::high_resolution_clock::now();
		long long start = std::chrono::time_point_cast
			<std::chrono::milliseconds>(startTime).time_since_epoch().count();
		long long end = std::chrono::time_point_cast
			<std::chrono::milliseconds>(endTime).time_since_epoch().count();
		std::cout << name << ":" << (end - start) << "ms\n";

		stopped = true;
	}

	~Timer() {
		if (!stopped) {
			Stop();
		}
	}
private:
	const char* name;
	std::chrono::time_point<std::chrono::steady_clock> startTime;
	bool stopped;
};

void Function1() {   //示例函数
	Timer timer("Function1");  
		for (int i = 0; i < 1000; i++) {
			std::cout <<"Hello World #" << i << std::endl;
	}
}

int main() {
	Function1();
	std::cout << "Hello world!" << std::endl;
	std::cin.get();
	return 0;
}

在测试函数类构建一个Timer对象让他开始计时,再离开函数作用时会自动调用析构函数停止计时并且输出耗时结果

但显然,他们仍是数字,而且在控制台寻找计时结果非常烦人,所以,有没有更好的方法呢?

通过edge://tracing!

我们可以可视化我们的分析和堆栈跟踪视图

使用微软自带的egde浏览器在你的url栏输入edge://tracing

如果你是谷歌浏览器就输入chrome://tracing就好了

别的浏览器我不太确定,你可以试试:对应浏览器英文名://tracing

进入这个页面通过Load加载一个包含所有相关数据的.json文件就可以可视化数据了。

但,我们如果获取c++项目里的计时相关.json数据呢?

首先在C++项目分别创建两个头文件填入如下代码

1.InstrumentorMacro.h

代码语言:cpp
复制
#pragma once
#define PROFILING 1
#if PROFILING
#define PROFILE_SCOPE(name) InstrumentationTimer timer##__LINE__(name)
#ifdef _MSC_VER
#define PROFILE_FUNCTION() PROFILE_SCOPE(__FUNCSIG__)
#else
#define PROFILE_FUNCTION() PROFILE_SCOPE(__PRETTY_FUNCTION__)
#endif
#else
#define PROFILE_SCOPE(name)
#define PROFILE_FUNCTION()
#endif

2.InstrumentorTimer.h

代码语言:cpp
复制
//
// Basic instrumentation profiler by Cherno
// ------------------------
// Modified by Gavin
// 1. Make 'WriteProfile' thread-safe by add 'std::mutex' and 'std::lock_guard'
// 2. Remove the 'InstrumentationSession' which is no significance
// 3. Add destructor for 'Instrumentor'
// 4. Change the Constructor of 'Instrumentor' to private
// ------------------------

// Usage: include this header file somewhere in your code (e.g. precompiled header), and then use like:
//
// Instrumentor::Get().BeginSession("Session Name");        // Begin session
// {
//     InstrumentationTimer timer("Profiled Scope Name");   // Place code like this in scopes you'd like to include in profiling
//     // Code
// }
// Instrumentor::Get().EndSession();                        // End Session
//
// You will probably want to macro-fy this, to switch on/off easily and use things like __FUNCSIG__ for the profile name.
//
#pragma once

#include <string>
#include <chrono>
#include <fstream>
#include <thread>
#include <mutex>
#include <algorithm>

struct ProfileResult {
    std::string Name;
    long long Start, End;
    uint64_t ThreadID;
};

class Instrumentor {
private:
    std::string m_SessionName;
    std::ofstream m_OutputStream;
    int m_ProfileCount;
    std::mutex m_Lock;
    bool m_ActiveSession;

public:

    ~Instrumentor() {
        EndSession();
    }

    Instrumentor(const Instrumentor&) = delete;

    static void BeginSession(const std::string& name, const std::string& filepath = "Results.json") {
        GetInstance().BeginSessionImpl(name, filepath);
    }

    static void EndSession() {
        GetInstance().EndSessionImpl();
    }

    static void WriteProfile(const ProfileResult& result) {
        GetInstance().WriteProfileImlp(result);
    }

private:

    Instrumentor()
        : m_SessionName("None"), m_ProfileCount(0), m_ActiveSession(false) {
        ;
    }

    static Instrumentor& GetInstance() {
        static Instrumentor instance;
        return instance;
    }

    void BeginSessionImpl(const std::string& name, const std::string& filepath = "Results.json") {
        if (m_ActiveSession) {
            EndSession();
        }
        m_ActiveSession = true;
        m_SessionName = name;

        m_OutputStream.open(m_SessionName + std::string("_") + filepath);
        WriteHeader();
    }

    void EndSessionImpl() {
        if (!m_ActiveSession) {
            return;
        }
        m_ActiveSession = false;
        m_ProfileCount = 0;

        WriteFooter();
        m_OutputStream.close();
    }

    void WriteHeader() {
        m_OutputStream << "{\"otherData\": {},\"traceEvents\":[";
        m_OutputStream.flush();
    }

    void WriteFooter() {
        m_OutputStream << "]}";
        m_OutputStream.flush();
    }

    void WriteProfileImlp(const ProfileResult& result) {
        std::lock_guard<std::mutex> lockGuard(m_Lock);

        if (m_ProfileCount++ > 0)
            m_OutputStream << ",";

        std::string name = result.Name;
        std::replace(name.begin(), name.end(), '"', '\'');

        m_OutputStream << "{";
        m_OutputStream << "\"cat\":\"function\",";
        m_OutputStream << "\"dur\":" << (result.End - result.Start) << ',';
        m_OutputStream << "\"name\":\"" << name << "\",";
        m_OutputStream << "\"ph\":\"X\",";
        m_OutputStream << "\"pid\":\"" << m_SessionName << "\",";
        m_OutputStream << "\"tid\":" << result.ThreadID << ",";
        m_OutputStream << "\"ts\":" << result.Start;
        m_OutputStream << "}";

        m_OutputStream.flush();
    }
};

class InstrumentationTimer {
private:
    const char* m_Name;

    std::chrono::time_point<std::chrono::high_resolution_clock> m_StartTimepoint;
    bool m_Stopped;

public:
    explicit InstrumentationTimer(const char* name)
        : m_Name(name), m_Stopped(false) {
        m_StartTimepoint = std::chrono::high_resolution_clock::now();
    }

    ~InstrumentationTimer() {
        if (!m_Stopped) {
            Stop();
        }
    }

    void Stop() {
        auto endTimepoint = std::chrono::high_resolution_clock::now();

        long long start = std::chrono::time_point_cast<std::chrono::microseconds>(
            m_StartTimepoint).time_since_epoch().count();
        long long end = std::chrono::time_point_cast<std::chrono::microseconds>(
            endTimepoint).time_since_epoch().count();

        uint64_t threadID = std::hash<std::thread::id>{}(std::this_thread::get_id());
        Instrumentor::WriteProfile({ m_Name, start, end, threadID });

        m_Stopped = true;
    }
};

在main函数所在的.cpp文件中

InstrumentorTimer.hInstrumentorMacro.h(可选的,一些宏定义)正确引入后。

代码语言:javascript
复制
Instrumentor::BeginSession("SessionName");               // Begin session 
{
    InstrumentationTimer timer("Profiled Scope Name");   // Place code like this in scopes you'd like to include in profiling
    // Code Blocks
    // timer.Stop();                                     // (Optional) Stop timing manually, timer's destructor will call this function automatically
}
// Instrumentor::EndSession();                           // (Optional) End Session manually, Instrumentor's destuctor will call this function automatically

则作用域内的代码会被正确记录到${SessionName}_Results.json文件中,该文件可在该项目.sln文件所在目录找到

在edge浏览器中进入edge://tracing页面,将该文件拖入即可看到可视化结果。

在将头文件引入后,在main函数中调用BeginSession()函数,在里面调用测试函数进行计时,不过在这之前需在测试函数域头部创建InstrumentationTimer的实例开始计时,当希望停止计时时调用该实例的Stop()函数即可,当作用域完成后该实例销毁,析构函数会检测当前是否已经完成计时,如果没有完成会自动调用Stop()函数。

代码例

代码语言:cpp
复制
#include<iostream>

#include "InstrumentorTimer.h"
#include "InstrumentorMacro.h"

void Function1() {
	std::string name = std::string("Function1");
	InstrumentationTimer timer(name.c_str()); //测试函数内必加行
	//Timer timer("Function1");
		for (int i = 0; i < 1000; i++) {
			std::cout <<"Hello World #" << i << std::endl;
	}
}

void Function2() {
	std::string name = std::string("Function2");
	InstrumentationTimer timer(name.c_str());//测试函数内必加行
	for (int i = 0; i < 100; i++) {
		std::cout << "Hello World2 #" << i << std::endl;
	}
}

int main() {
	Instrumentor::BeginSession("SessionName");               // Begin session 
	{
		Function1();  //测试函数1
		Function2();  //测试函数2
		InstrumentationTimer timer("Profiled Scope Name");   // Place code like this in scopes you'd like to include in profiling
		// Code Blocks
		// timer.Stop();                                     // (Optional) Stop timing manually, timer's destructor will call this function automatically
	}
	//std::cin.get();
	return 0;
}

当然不止是单线程函数检测,还可以检测多线程

笔者线程相关了解肤浅,无妨能用就行。

多线程代码例:

代码语言:cpp
复制
#include <iostream>
#include<thread>

#include "InstrumentorTimer.h"
#include "InstrumentorMacro.h"

void DoWork() {
	std::string name = std::string("DoWork");
	InstrumentationTimer timer(name.c_str()); //必加行
	int x = 2;
	using namespace std::literals;

	std::cout << "Starting thread id=" << std::this_thread::get_id() << std::endl;

	while (x) {
		std::cout << x << "Working..." << std::endl;
		std::this_thread::sleep_for(1s);
		x--;
	}
}

void Function1() {
	std::string name = std::string("Function1");
	InstrumentationTimer timer(name.c_str());
	//Timer timer("Function1");
		for (int i = 0; i < 1000; i++) {
			std::cout <<"Hello World #" << i << std::endl;
	}
}

void Function2() {
	std::string name = std::string("Function2");
	InstrumentationTimer timer(name.c_str());
	for (int i = 0; i < 100; i++) {
		std::cout << "Hello World2 #" << i << std::endl;
	}
}

int main() {
	Instrumentor::BeginSession("SessionName");               // Begin session 
	{
		std::thread worked1(Function1);  //测试函数1
		Function2();  //测试函数2
		std::thread worked2(DoWork);
		worked2.join();
		worked1.join();
		InstrumentationTimer timer("Profiled Scope Name");   // Place code like this in scopes you'd like to include in profiling
		// Code Blocks
		// timer.Stop();                                     // (Optional) Stop timing manually, timer's destructor will call this function automatically
	}
	//std::cin.get();
	return 0;
}

图片例:

参考例:

Github上的:InstrumentorTimer

Github上的:Basic Instrumentation Profiler (github.com)

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通过edge://tracing!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档