10 TECHNIQUES TO UNDERSTAND CODE YOU DONT KNOW


作者是Jonathan Boccara, Fluent C++作者,这个PPT就是卖书的

大纲

  • 探索
  • 速读
  • 理解细节

探索

  1. IO框架 是什么样的?
  2. 主要代码片
  3. 分析堆栈
    1. 主要路径
    2. 火焰图

速读

  1. 开头结尾,找重点信息,目的不是看所有的信息,找输入输出的流动
  2. 关键词,频率出现比较高的词,可能就是主要逻辑
  3. 关注控制流程
  4. 找主要的动作

理解代码细节

  1. 代码解耦,小函数封装。小规模重构
  2. 识别出复杂的没有外部依赖的代码片,这种都是写的烂,专攻这个重构
  3. 结对编程,组队review

ref

  1. PPT

contact

Read More

折腾了一下darknet

如果darknet要支持GPU和CUDNN的话,会有很多坑。

安装CUDA 两种方式,下载安装包和安装软件源

具体在https://developer.nvidia.com/cuda-downloads

我选的是网络安装deb

首先要下载deb文件,然后执行上面的步骤,cuda 就安装好了,默认在环境变量内。不用修改Makefile

如果是手动安装软件包,需要改动makefile img

COMMON需要改正安装的路径 安装结束后,需要注意修改nvcc路径,不在环境变量中可能会识别不到,改下路径

安装CUDNN 这个没有办法,不能用命令行

https://developer.nvidia.com/rdp/cudnn-download

img

点第一个就可以(需要注册)

tar -zxvf cudnn-9.2-linux-x64-v7.1.tgz
cp cuda/include/cudnn.h /usr/local/cuda/include/
cp cuda/lib64/* /usr/local/cuda/lib64/

然后编译就可以了

Read More


(cppcon)using types effectively

cppcon2016 using types effectively

本来还在看cppcon2014,偶然翻到个和类型相关的演讲,以为是以为是PLT那种东西。学习过之后发现还是讨论的代数类型

sum type 和product type就不说了,主要是类型带来的重复和内耗,c++17带来了std::optional和 std::variant ,组织状态就可以用这俩,放弃原来的switch做法,转用match,缩小范围,全变成类型,更可控

中间有大量的篇幅推导product type的数量级,函数的数量级是指数级!

还讨论了个小插曲,在lua中1==true结果为false,因为不是同一个类型

作者的一个改造例子

原有方案

enum class ConnectionState{
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
    CONNECTION_INTERRUPTED
};
struct Connection{
    ConnectionState m_connectionState;
    std::string m_serverAddress;
    std::chrono::system_clock::time_point m_connectedTime;
    std::chrono::millisecondes m_lastPingTime;
    Timer m_reconnectTimer;
};

在看改造后

struct Connection{
    std::string m_serverAddress;
    struct Disconnected{};
    struct Connecting{};
    struct Connected{
        ConnectionId m_id;
        std::chrono::system_clock::time_point m_connectedTime;
        std::chrono::millisecondes m_lastPingTime;
    };
    struct ConnectionInterrupted{
        std::chrono::system_clock::time_point m_disconnectedTime;
        Timer m_reconnectTimer;
    };
    std::variant<Disconnected,Connecting,Connected,ConnectionInterrupted> m_connection;
};

再举一个例子

class Friend{
    std::string m_alias;
    bool m_aliasPopulated;
};

两个字段到处同步,坑爹 -> std::optional<string> m_alias

ref

  • [https://github.com/CppCon/CppCon2016/blob/master/Tutorials/Using%20Types%20Effectively/Using%20Types%20Effectively%20-%20Ben%20Deane%20-%20CppCon%202016.pdf
Read More

(cppcon)Declarative Control Flow

这个讲的是scope_exit和栈回溯异常处理问题,用上了std::uncaught_exceptions

class UncaughtExceptionCounter {
	int getUncaughtExceptionCount() noexcept;
	int exceptionCount_ ;
public:
	UncaughtExceptionCounter()
	: exceptionCount_ (std::uncaught_exceptions()) {
	}
	bool newUncaughtException() noexcept {
		return std::uncaught_exceptions() > exceptionCount _ ;
	}
};

template <typename FunctionType, bool executeOnException>
class ScopeGuardForNewException {
	FunctionType function_ ;
	UncaughtExceptionCounter ec_ ;
public:
	explicit ScopeGuardForNewException(const FunctionType& fn)
	: function_ (fn) {}
	explicit ScopeGuardForNewException(FunctionType&& fn)
	: function_ (std::move(fn)) {}
	~ScopeGuardForNewException() noexcept(executeOnException) {
		if (executeOnException == ec_.isNewUncaughtException()) {
			function_ ();
		}
	}
};

enum class ScopeGuardOnFail {};

template <typename FunctionType>
ScopeGuardForNewException<typename std::decay<FunctionType>::type, true>
operator+(detail::ScopeGuardOnFail, FunctionType&& fn) {
	return ScopeGuardForNewException<
		typename std::decay<FunctionType>::type, true>(
			std::forward<FunctionType>(fn));
}

#define SCOPE_FAIL \
	auto ANONYMOUS_VARIABLE(SCOPE_ FAIL_STATE) \
		= ::detail::ScopeGuardOnFail() + [&]() noexcept

我的疑问,直接用scope_exit不行吗,貌似这个捕获了其他异常也会执行functor,不局限于本身的fail

需求不太一样

ref

看到这里或许你有建议或者疑问,我的邮箱wanghenshui@qq.com 先谢指教。

Read More

(cppcon)c++11 几个生产中常用的小工具

这个讲的是scope_exit, std::range, make_range , operator <=> ,后面这两个已经进入c++标准了,前面这个有很多案例

先说scope_exit

作者写了个宏,显得scope_exit更好用一些,实际上没啥必要

他的实现类似这个

 template <typename Callable> class scope_exit {
   Callable ExitFunction;
   bool Engaged = true; // False once moved-from or release()d.
 
 public:
   template <typename Fp>
   explicit scope_exit(Fp &&F) : ExitFunction(std::forward<Fp>(F)) {}
 
   scope_exit(scope_exit &&Rhs)
       : ExitFunction(std::move(Rhs.ExitFunction)), Engaged(Rhs.Engaged) {
     Rhs.release();
   }
   scope_exit(const scope_exit &) = delete;
   scope_exit &operator=(scope_exit &&) = delete;
   scope_exit &operator=(const scope_exit &) = delete;
 
   void release() { Engaged = false; }
 
   ~scope_exit() {
     if (Engaged)
       ExitFunction();
   }
 };
 

几个疑问

  • 为什么直接保存Callable,而不是用 std::function来保存
    • 太重,引入类型擦除,效率不行,生成的汇编很差
    • 作者还稍稍科普了下类型擦除的技术,怎么做到的
  • 为什么不用AA的scopeguard或者boost::scope_exit
    • 不好用,还需要传参数,scope_exit好就好在可以传lambda

第二个是make_iterable,对应标准库应该是std::range 本质上还是视图一类的东西。因为自定义类型,不想写一套iterator接口,给个view就行。没啥说的

第三个是operator<=> 实际上是解决comparator这种语义实现的问题

就比如rocksdb,bytewisecomparator

  int r = memcmp(data_, b.data_, min_len);
  if (r == 0) {
    if (size_ < b.size_) r = -1;
    else if (size_ > b.size_) r = +1;
  }
  return r;

内部肯定有三个分支,这是影响效率的,怎么搞?

再比如std::tuple 他是怎么比较的?

然后推导出一个适当的operator<=>来解决上述问题

ref

Read More

(cppcon)用表达式模板实现一个一个简单安全的log

传统写法

#define LOG(msg) \
    if (s_bLoggingEnabled) \
       std::cout<<__FILE__<<"("<<__LINE__<<")"<<msg<<std::endl;

汇编长这样

   0x0000000000400c9d <+53>:    mov    %rax,%rdi
   0x0000000000400ca0 <+56>:    callq  0x400af0 <_ZNSaIcED1Ev@plt>
   0x0000000000400ca5 <+61>:    movzbl 0x201528(%rip),%eax        # 0x6021d4 <s_bLoggingEnabled>
   0x0000000000400cac <+68>:    test   %al,%al
   0x0000000000400cae <+70>:    je     0x400d2c <main()+196>
   0x0000000000400cb0 <+72>:    mov    $0x400e5a,%esi
   0x0000000000400cb5 <+77>:    mov    $0x6020c0,%edi
   0x0000000000400cba <+82>:    callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400cbf <+87>:    mov    $0x400e65,%esi
   0x0000000000400cc4 <+92>:    mov    %rax,%rdi
   0x0000000000400cc7 <+95>:    callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400ccc <+100>:   mov    $0xe,%esi
   0x0000000000400cd1 <+105>:   mov    %rax,%rdi
   0x0000000000400cd4 <+108>:   callq  0x400a70 <_ZNSolsEi@plt>
   0x0000000000400cd9 <+113>:   mov    $0x400e67,%esi
   0x0000000000400cde <+118>:   mov    %rax,%rdi
   0x0000000000400ce1 <+121>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400ce6 <+126>:   mov    $0x400e69,%esi
   0x0000000000400ceb <+131>:   mov    %rax,%rdi
   0x0000000000400cee <+134>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400cf3 <+139>:   mov    %rax,%rdx
   0x0000000000400cf6 <+142>:   lea    -0x40(%rbp),%rax
   0x0000000000400cfa <+146>:   mov    %rax,%rsi
   0x0000000000400cfd <+149>:   mov    %rdx,%rdi
   0x0000000000400d00 <+152>:   callq  0x400b50 <_ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKNSt7__cxx1112basic_stringIS4_S5_T1_EE@plt>
   0x0000000000400d05 <+157>:   mov    $0x37,%esi
   0x0000000000400d0a <+162>:   mov    %rax,%rdi
   0x0000000000400d0d <+165>:   callq  0x400a70 <_ZNSolsEi@plt>
   0x0000000000400d12 <+170>:   mov    $0x400e6d,%esi
   0x0000000000400d17 <+175>:   mov    %rax,%rdi
   0x0000000000400d1a <+178>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400d1f <+183>:   mov    $0x400b10,%esi
   0x0000000000400d24 <+188>:   mov    %rax,%rdi

作者说了log相关的指令问题,可能会阻止编译器优化,icache也不友好

针对此,要达到减少指令,保留速度,类型安全还方便,所以就用到表达式模板了,把工作放到编译期

怎么做?把所有打印的参数用表达式墨宝封装一下,typelist登场

拆成两部分,log 和logdata

logdata就是个表达式模板typelist,把所有的参数串起来

using namespace std;
#define LOG(msg) \
    if (s_bLoggingEnabled) \
       (log(__FILE__,__LINE__,LogData<None>()<<msg));

template <typename List>
struct LogData{
    typedef List type;
    List list;
};
struct None{};

template <typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin,
                                                Value&& v) noexcept {
    return ;
}

template <typename Begin, size_t n >
LogData<std::pair<Begin&&, const char* >> operator<<(LogData<Begin>&& begin,
                                               const char(&sz)[n]) noexcept{
    return ;
}

template<typename TLogData>
void log(const char* file, int line, TLogData&& data)
noexcept {
    std::cout<< file<<" "<<line<<": ";
    LogRecursive(std::cout, std::forward<typename TLogData::type>(data.list));
    std::cout<<std::endl;
}

template<typename TLogDataPair>
void LogRecursive(std::ostream& os, TLogDataPair&& data) noexcept{
   LogRecursive(os,std::forward<typename TLogDataPair::first_type>(data.first));
   os<<std::forward<typename TLogDataPair::second_type>(data.second);
}
inline void LogRecursive(std::ostream& os, None) noexcept{}

int main()
{
  s_bLoggingEnabled = true;
  string s{"blaa"};
  LOG("sth"<<s<<55<<"!!");
}

   0x0000000000400cab <+67>:    movzbl 0x201522(%rip),%eax        # 0x6021d4 <s_bLoggingEnabled>
   0x0000000000400cb2 <+74>:    test   %al,%al
   0x0000000000400cb4 <+76>:    je     0x400d42 <main()+218>
   0x0000000000400cba <+82>:    movl   $0x37,-0x44(%rbp)
   0x0000000000400cc1 <+89>:    lea    -0x11(%rbp),%rax
   0x0000000000400cc5 <+93>:    mov    $0x40130a,%esi
   0x0000000000400cca <+98>:    mov    %rax,%rdi
   0x0000000000400ccd <+101>:   callq  0x400e24 <operator<< <None, 4ul>(LogData<None>&&, char const (&) [4ul])>
   0x0000000000400cd2 <+106>:   mov    %rax,-0x30(%rbp)
   0x0000000000400cd6 <+110>:   mov    %rdx,-0x28(%rbp)
   0x0000000000400cda <+114>:   lea    -0xa0(%rbp),%rdx
   0x0000000000400ce1 <+121>:   lea    -0x30(%rbp),%rax
   0x0000000000400ce5 <+125>:   mov    %rdx,%rsi
   0x0000000000400ce8 <+128>:   mov    %rax,%rdi
   0x0000000000400ceb <+131>:   callq  0x400ebd <operator<< <std::pair<None&&, char const*>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(LogData<std::pair<None&&, char const*> >&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)>
   0x0000000000400cf0 <+136>:   mov    %rax,-0x40(%rbp)
   0x0000000000400cf4 <+140>:   mov    %rdx,-0x38(%rbp)
---Type <return> to continue, or q <return> to quit---
   0x0000000000400cf8 <+144>:   lea    -0x44(%rbp),%rdx
   0x0000000000400cfc <+148>:   lea    -0x40(%rbp),%rax
   0x0000000000400d00 <+152>:   mov    %rdx,%rsi
   0x0000000000400d03 <+155>:   mov    %rax,%rdi
   0x0000000000400d06 <+158>:   callq  0x400f6a <operator<< <std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>, int>(LogData<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&> >&&, int&&)>
   0x0000000000400d0b <+163>:   mov    %rax,-0x60(%rbp)
   0x0000000000400d0f <+167>:   mov    %rdx,-0x58(%rbp)
   0x0000000000400d13 <+171>:   lea    -0x60(%rbp),%rax
   0x0000000000400d17 <+175>:   mov    $0x40130e,%esi
   0x0000000000400d1c <+180>:   mov    %rax,%rdi
   0x0000000000400d1f <+183>:   callq  0x401002 <operator<< <std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>, 3ul>(LogData<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&> >&&, char const (&) [3ul])>
   0x0000000000400d24 <+188>:   mov    %rax,-0x70(%rbp)
   0x0000000000400d28 <+192>:   mov    %rdx,-0x68(%rbp)
   0x0000000000400d2c <+196>:   lea    -0x70(%rbp),%rax
   0x0000000000400d30 <+200>:   mov    %rax,%rdx
   0x0000000000400d33 <+203>:   mov    $0x31,%esi
   0x0000000000400d38 <+208>:   mov    $0x401311,%edi
   0x0000000000400d3d <+213>:   callq  0x401054 <log<LogData<std::pair<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>&&, char const*> > >(char const*, int, LogData<std::pair<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>&&, char const*> >&&)>
   0x0000000000400d42 <+218>:   lea    -0xa0(%rbp),%rax

我个人测验,感觉并没有好到哪里去。。gcc开O3的话两个长得基本一致了。。就当学一下typelist转发技术好了

ref

Read More

(cppcon)return values take a closure walk

这个ppt讲的是如何把返回值用lambda包起来,让返回值auto,用作者的图来总结这篇内容

1556158688788

首先思考在Context下的调用

void callWithin(const std::function<void()>& fn){
    ScopedContext context;
    try{
        fn();
    } catch (SomeException& e){
        // handle exception here
    }
}

void printLine(const std::string& text){
    std::cout<<text<<'\n';
}
callWithin([](){printLine("Hello, CppCon");});

回调小函数扔到lambda里 接口都操作lambda

也可以把这个变成模板, 这样 接口可以是任何类型的std::function, lambda

template <typename Callable>
void callWithin(const Callable& fn){
    ScopedContext context;
    fn();
}

进一步,如果想要回调函数的返回值,不需要要变动lambda接口

double sum(double a,double b){return a+b;}
double res = callWithin([](){return sum(3.14,2.71);})

可以在callWithin里改动lambda/function接口,但这降低了灵活性

double callWithin(const std::function<double()>&fn)...//如果返回值不是double怎么办?

解决办法,template auto-decltype

template <typename Callable> 
auto callWithin(const Callable& fn) ->decltype(fn()){
   decltype(fn()) result{};
   auto wrapperFn =[&]()->void
   {
       result = fn();
   }
   callWithImpl(wrapperFn);
   return result;
}
void callWithinImpl(const std::function<void()>& fn);

注意,这里用局部变量封一层result,弄一个局部的lambda,然后扔进callWithinImpl里,本质是加了一层,把原来的lambda放return的方案内嵌处理

传统方法,context肯定会有context manager,通过manager类接口来搞定, 接口也是固定的

class Contextmanager{
public:
    virtual void callWithin(const std::function<void()>&fn) = 0;
};

然后整合上面的实现,大致这样

class Contextmanager{
public:
  template <typename Fn>
  void callWithin(const Fn& fn, std::false_type) -> decltype(fn())
  {
    decltype(fn()) result{};
    callWithinImpl([&]{result=fn();});
    return result;
  }
private:
    virtual void callWithinImpl(const std::function<void()>&fn) = 0;
};

double result = manager->CallWithin([]{return sum(3.14, 2/71);});

这个方案又有了新问题,原来的直接传void返回的functor不能工作了

特化吧

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn())
{
    return _callWithin(fn, std::is_same<decltype(fn()),void>());
}
// true就直接调用,没有返回值
template <typename Fn>
void _callWithin(const Fn& fn, std::true_type) -> decltype(fn())
{
    callWithinImpl([&]{fn();});
}

template <typename Fn>
void _callWithin(const Fn& fn, std::false_type) -> decltype(fn())
{
    decltype(fn()) result{};
    callWithinImpl([&]{result=fn();});
    return result;
}

新的挑战,callWithin失败

所以还是需要内部callWithinImpl有个返回值,来设定result,需要std::optional 包装一下

template <typename Fn>
void callWithin(const Fn& fn) -> std::optional<decltype(fn())>
{
    decltype(fn()) result{};
    bool ok = callWithinImpl([&]{result=fn();});
    if (ok)
    	return result;
    else
        return std::nullopt;
}

ref

Read More

^