R

Yihui Xie: R Markdown: The Definitive Guide

谢益辉最近整理了一份 Rmarkdown 的技术资料,详细介绍了使用 Rmarkdown 进行 ‘literate programming’(附带说明的编程方法),以及如何开发拓展插件。

reticulate

reticulate 是一个连接 RPython 通信接口的插件,实现了在 R 编程环境中调用 Python 命令。

马上(2020年)就是 R 开发 20 周年了。最近网上大家都在讨论 R 编程语言开发的未来方向,是偏重运行效率(c)、还是侧重在数据分析(stat)?

In the case of the wildly popular statistical programming language R, it’s been more about revolution than evolution. The changes keep coming.

今天在网上看到一篇大神的访谈节选,个人认为比较清楚的说明了未来如何利用 R 进行开发的主要方向。data.table 是一个专注性能的数据分析软件包,在处理大数据方面具有显著的效率优势,未来的方向,可能是在 data.table 这个数据类型的基础上,进行优化。

I have just been working on this thing called “dtplyr” which will allow people to write in “dplyr” code and than it will translate it into data.table code. *(Edit*or’s note: dplyr and data.table are popular libraries for data analysis in R.)

此外,现在似乎有一个不好的风气,就是在 RPython 社区之间存在敌对分裂的声音。其实,二者作为数据科学的两把利剑,可以在各自擅长的领域发挥功能。比如,我经常使用 R 进行数据清洗、数据分析,然后再利用 Python 写入交易系统,并交给了 C++ 的订单系统执行任务。

My hope is that the integrations between R and Python continue to grow and for R to more seamlessly fit in the data science workflow. There are certain things that it will never be as good at as Python, and we want to make sure people can collaborate with Python people. But also we want to provide the basics in R, so there is a choice. If you want cutting edge stuff, you can use whatever tool you want, but R should be good enough at nearly everything that you can stay in R if you want.

dtplyr

上文提高大神最近在开发 dtplyr,我个人比较多的使用 data.table 的语法,dplyr 由于性能方面的原因没怎么使用。

dtplyr provides a data.table backend for dplyr. The goal of dtplyr is to allow you to write dplyr code that is automatically translated to the equivalent, but usually much faster, data.table code.

Python

使用 root 给所有用户安装 Anaconda

安装 Anaconda 的时候记得选择路径,/opt/anaconda3

## Add the anaconda PATH to /etc/profile
PATH=/opt/anaconda3/bin:$PATH

source /etc/profile

JupyterLab 服务器端口转发

  1. 登录终端后,设置 Jupyter 密码

    admink@admin:~$ jupyter notebook --generate-config
    Writing default config to: /home/admink/.jupyter/jupyter_notebook_config.py
    admink@admin:~$
    admink@admin:~$ jupyter notebook password
    Enter password:
    Verify password:
    [NotebookPasswordApp] Wrote hashed password to /home/admink/.jupyter/jupyter_notebook_config.json
    
    1. 开启 Jupyter
    admink@admin:~$ nohup jupyter notebook --no-browser &
    [4] 21913
    admink@admin:~$ nohup: ignoring input and appending output to 'nohup.out'
    

    列出当前的连接

    admink@admin:~$ jupyter notebook list
    Currently running servers:
    http://localhost:8888/ :: /home/admink
    
    1. 在本地终端打开,进行端口映射,输入密码(ssh 密码)。这里,我假定映射端口是 9888
    ssh -N -L 9888:localhost:8888 admink@114.67.109.5 -p9101
    admink@114.67.109.5's password:
    
  2. 这样,我们就可以在本地浏览器打开进行操作了:http://localhost:9888/。输入的密码是 jupyter password 的密码,建议和 ssh 密码一致,免得搞混淆了。

C/C++

知乎:以C++为核心语言的高频交易系统是如何做到低延迟的?

这里不做评论,只引用原文:

作者:panda

虽然是一个老问题,但这确实是一个很有意思的问题。凑个热闹,根据自己的经验来谈谈。(友情提示,重头戏在本文结尾处)

Scott Meyers曾经说过这么一句话“if you are not at all interested in performance, shouldn’t you be in the Python room down the hall”。系统性能对交易系统的重要性不言而喻,而低时延对高频系统来说,就是非常重要的系统性能之一,哪怕说是最重要的也不为过。

那么在回答怎么才能做到低时延的时候,我们首先需要知道,对于一个高频交易系统来说,latency方面最大的bottleneck依次是哪些。而要知道latency的bottleneck,你又需要一个合理的测量时延的测试环境,这方面陈硕的回答已经很赞,我就不赘述了。

很有意思的是,通常取得最好效果提升的地方却是与编程语言无关的。比如,网络时延一般就是延迟方面最大的bottleneck之一。所以为了降低网络时延,我们需要colo,需要物理上与交易所的撮合机越近越好,需要高的带宽和最快的nic卡及其模式(比如选择合适的openload模式)。更高阶的,可以考虑使用fpga,或者是定制的nic卡。

与上一条息息相关的,其实就是在数据,比如市场信息,进入cpu之前(当然那种fpga进,fpga出的特殊解决方案除外),尽量减少数据拷贝以及context switches。比如,大家经常提到的Solarflare的nic卡就是通过interrupt kernel来达到kernel bypassing的效果。

以上这些说到底是为了解决更快的I/O,但这还不够,还进入讨论具体程序之前,需要一系列的server tunning。这里我只提几个比较显而易见的:1. disable hyperthreading,2. turn on over clocking, 3. disable Nagle’s algorithm,4. set cpu affinity and isolation。

代码实现方面,大概可以参考以下几点(但不限于此):

  1. 对于低时延系统,能用单线程解决问题,就千万不要多线程。

\2. 一定要有一个hot-path的概念,在它范围内的代码需要仔细优化。当然,hot-path对于core dev和strategy dev的概念可能是有些许不同的。

\3. 尽量让run-time的数据处理变得简单。在C++里面, 那就是template metaprogramming。能用CRTP的地方就别用dynamic polymorphism。能用expression templates来帮助计算的,就可以考虑使用它。

\4. 尽量避免run-time的memory allocation。可以考虑重复使用同类的object,或者是memory pool,这样可以避免overhead,也可以减少memory fragmentation。

\5. 要了解自己待处理的数据,这样在一定条件下可以允许undefined behavior的存在。比如,vector[] vs vector.at()。对于一个sub-microsecond级别的系统,safety check有时候都会expensive。

\6. 利用好cache。基本的规则大概就是: 能在cache里面存下data和instructions,就不用access main memory,能在registers里面存下,就不要access cache。尽量使用contiguous blocks of memory,这也是为什么Bjarne Stroustrup本人也会推荐大家优先考虑使用vector。至于怎么写cache friendly的代码,可以参考这个: http://cppatomic.blogspot.com/2018/02/cache-friendly-code.html。

\7. 注意好struct padding。也要留意在多线程情况下会出现的false sharing情况。

\8. 避免不必要的branch和table lookup。使用virtual functions和大量叠加的if语句,都有可能增加cache misses和pipeline clearances的可能性。

\9. 用好编译器提供的builtins,像是expected,prefetch之类。

\10. 得了解编译器和连接器在做什么。比如,最好不要简单的假设-O2就可以帮你解决全部问题。有时候,O2/O3的优化,因为各种原因,反而会让代码变慢。比如: GCC fails to optimize aligned std::array like C array

\11. 大多数情况下,大家还是会首选用STL里面的container,但是还是需要谨慎,比如std::undered_map的性能对于低时延系统就不够用。

以上这些,虽然写下来看着感觉都不难,但都需要一定程度的经验积累,而如果有乐于分享经验的朋友或者同事,那可能就会事半功倍了,所谓他山之石可以攻玉!好了,重头戏来了!让我们来看看著名高频公司optiver的senior dev在cppcon17上都和我们分享了什么: https://www.youtube.com/watch?v=NH1Tta7purM。

其实有这样的同事真的是好事,比如DRW前员工Matt Godbolt就非常喜欢分享,他也有自己的channel: https://www.youtube.com/watch?v=fV6qYho-XVs。

另外一个回答也不错:

作者:闵康

我们的程序的响应时间是10us(从收到行情到发出报单的响应时间),但是ping期货公司的交易前置机需要大约30us【这个数值会变化,见注释4】,所以网络延时占据了大量时间。

我所有的性能测试都是在一台DELL r630机器上运行的,这台机器有2个NUMA结点,CPU型号是E5 2643 v4(3.4GHz 6核)。所有的测试都是用rdtsc指令来测量时间,Intel官网上有一篇pdf文档[Gabriele Paoloni, 2010],讲述了如何精准地测量时间(要用cpuid来同步)。我自己做的性能测试的结果会写成“100(sd20)ns”的形式,代表平均值是100ns,标准差是20ns。我在算均值和标准差的时候会去掉最大的0.1%的数据再算,因为那些数据似乎并不是程序延时,而是cpu被调度执行别的任务了【原因见注释3】。有些性能测试在网上有现成的测试结果,我就没自己测,直接拿来用了,但是以后我会重新在我的机器上测一遍。

一些我们比较注意的点:

1.限制动态分配内存

相关的知识背景:glibc默认的malloc背后有复杂的算法,当堆空间不足时会调用sbrk(),当分配内存很大时会调用mmap(),这些都是系统调用,似乎会比较慢,而且新分配的内存被first touch时也要过很久才能准备好。

可取的做法:尽量使用vector或者array(初始化时分配足够的空间,之后每次使用都从里面取出来用)。尽量使用内存池。如果需要二叉树或者哈希表,尽量使用侵入式容器(boost::intrusive)。

性能测试:我测试的分配尺寸有64和8128两种。首先,我测试了glibc malloc的性能,分配64字节耗时98(sd247)ns,分配8128字节需要耗时1485(sd471)ns。其次,我写了一个多进程安全的内存池,分配64字节需要29(sd15)ns,分配8128字节需要22(sd12)ns。【内存池的细节见注释6】。最后,我单独测试了sbrk()和first touch的性能,但是数据不记得了。

2.使用轮询,尽量避免阻塞

相关的知识背景:上下文切换是非常耗时的,其中固定的消耗包括(cpu流水线被冲掉、各种寄存器需要被保存和恢复、内核中的调度算法要被执行),此外,缓存很有可能出现大量miss,这属于不固定的时间消耗。

可取的做法:使用带有内核bypass功能的网卡。每个进程或者线程都独占一个cpu核【isolcpus和irqbalance的细节见注释3】,并且不停地轮询,用以保证快速响应。尽量避免任何可能导致阻塞的事件(如mutex),某些注定很慢的活动(比如把log写到磁盘上)应该被独立出来放到别的cpu上,不能影响主线程。

性能测试:网上有一篇博客[tsunanet, 2010]测试了mode switch、thread switch、process switch的耗时,但是这篇文章太早了,以后我要用我的新cpu重新测一下。这篇博客里面,系统调用只需要<100ns,线程/进程切换需要>1us(不包括缓存miss的时间)。

3.使用共享内存作为唯一的IPC机制

相关的知识背景:共享内存只有在初始化的时候有一些系统调用,之后就可以像访问正常内存一样使用了。其他IPC机制(管道、消息队列、套接字)则是每次传输数据时都有系统调用,并且每次传输的数据都经历多次拷贝。因此共享内存是最快的IPC机制。

可取的做法:使用共享内存作为唯一的IPC机制。当然,可能需要手动实现一些东西来保证共享的数据在多进程下是安全,我们是自己实现了无锁内存池、无锁队列和顺序锁【关于seqlock的疑点见注释1】。

性能测试:我使用了boost中的Interprocess库和Lockfree库,在共享内存上建立了一个spsc队列,然后用这个队列来传送数据,代码参考了stackoverflow上的一个答案[sehe, 2014]。我传送的数据是一个8字节整数,延时是153(sd61)ns。至于其他IPC机制,我在[cambridge, 2016]看到了一些性能测试结果,通常是要几微秒到几十微秒不等。

4.传递消息时使用无锁队列

相关的知识背景:我只关注基于数组的无锁队列,其中:spsc队列是wait-free的,不论是入队出队都可以在确定的步数之内完成,而且实现时只需要基本的原子操作【为什么这很重要见注释7】;mpmc队列的实现方式则多种多样,但都会稍微慢一点,因为它们需要用一些比较重的原子操作(CAS或者FAA),而且有时它们需要等待一段不确定的时间直到另一个线程完成相应操作;另外,还有一种multi-observer的『广播队列』,多个读者可以收到同一条消息广播,这种队列也有sp和mp类型的,可以检查或者不检查overwrite;最后,还有一种队列允许存储不定长的消息。

可取的做法:总的来说,应该避免使用mp类型的队列,举例:如果要用mpsc队列,可以使用多个spsc来达成目的,并不需要mp队列;同理,如果是消息广播,也可以使用多个sp队列来取代一个mp队列;如果广播时observer只想订阅一部分消息,那么可以用多个spsc+有计数功能的内存池【具体做法见注释2】;如果要求多个观察者看到多个生产者的消息,并且顺序一致,那只能用mp队列了。总结一下,mp类型的队列应该尽量避免,因为当多个生产者同时抢占队列的时候,延时会线性增长。

性能测试:我写了一个mp类型的广播队列,传输的数据是8字节int,当只有一个生产者时,传输的延时是105(sd26)ns。增加观察者会使延时略微变大,增加生产者会使延时急剧变大(我用rdtsc指令控制不同生产者同时发送消息)。对于这个队列来说,它的延时只略高于跨核可视延时【测试结果见注释8】,所以应该算是不错了。

5.考虑缓存对速度的影响

相关的背景知识:现在的机器内存是十分充足的,但是缓存还是很小,因此所有节省内存的技巧都还有用武之地。

可取的做法:尽量让可能被同时使用的数据挨在一起;减少指针链接(比如用array取代vector,因为链接指向的地方可能不在缓存里);尽量节省内存(比如用unique_ptr取代vector,比如成员变量按照从大到小排序,比如能用int8的地方就不用int16);指定cpu affinity时考虑LLC缓存(同核的两个超线程是共享L1,同cpu的两个核是共享L3,不同NUMA核是通过QPI总线);会被多个核同时读写的数据按照缓存行对齐(避免false sharing)。

【注释1】:有一篇惠普的论文[Hans-J.Boehm, 2012]大致叙述了顺序锁的实现方法,但是那里面有两点让我感到困惑。一是需要用到thread_fence,这在某些cpu上可能会影响性能(x86似乎没影响);二是被保护的内容也必须是原子变量(可以是多个原子变量,所以被保护的内容可以很长)。但这是我见过的唯一一个符合C++标准的SeqLock的实现。

【注释2】:如果有M个生产者要发消息给N个观察者,可以建M*N个spsc队列和M个内存池,观察者只能读内存池里的数据,只有对应的那一个生产者可以修改内存池。我感觉这样应该会更快,但我没测过。

【注释3】:isolcpus可以隔离出一些cpu,避免其他线程被调度到这些cpu上执行。此外,设置irq affinity可以让一些cpu尽量避免响应中断,但在/proc/interrupts里面仍然有一些项目是避免不了的,而cpu处理中断时,用户程序会有一段时间(有时高达几十微秒)无法响应,我们没法解决这个问题。

【注释4】:在不同的时间点,ping的结果会有很大差异。交易时间段内ping出来的结果是30us,其它时间段ping出来的结果可能是几百微秒。我不知道这是什么原因,可能是期货公司为了省电关掉了某些东西?

【注释6】:我们要在共享内存上使用内存池,所以不得不自己写一个。我写的内存池只能分配固定尺寸的内存块,但是用户可以建立好几个内存池,用来分配不同的尺寸。实现的过程中有两个要点。一是用无锁链表来保存空闲的内存块;二是每个线程内部有一个缓冲区,所以真正取内存块的时候是没有CAS操作的。

【注释7】:在Intel x86的cpu上,如果C++中的内存顺序只用了acquire和release,那么编译出来的汇编代码里面不会有任何内存栅栏指令;如果同时也没有RMW(读-改-写)指令的话,无锁的代码编译出来就会像是普通的代码一样了。事实上,spsc队列的延时几乎等于跨核可视延时。

【注释8】:跨核可视延时:对于一个共享变量来说,如果有一个核上面的进程或者线程修改了这个变量,另一个核需要过一段时间才能看到这个修改,这段时间被称作跨核可视延时。我不确定在这段时间内,第二个核是会看到旧的数据还是这条指令会执行很久。在我的机器上,对于同一个cpu上的不同核心,这个值是96(sd14)ns。另外,对于同一个核心上的不同超线程,这个值应该会更小;对于同一台机器上的不同cpu,这个值应该会更大。

[cambridge, 2016]:Computer Laboratory

[Gabriele Paoloni, 2010]:Code Execution Times: IA-32/IA-64 Instruction Set Architecture

[Hans-J.Boehm, 2012]:http://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf

[sehe, 2014]:Shared-memory IPC synchronization (lock-free)

[tsunanet, 2010]:http://blog.tsunanet.net/2010/

珠海宽德

周五接到的通知,今天一大早来珠海宽德公司。在这之前,有过一轮HR电话一轮,主要对背景进行询问、一轮技术肖总监的电话面试、一轮技术副总监的电话面试、以及一轮和肖总监在深业上城muji店咖啡厅的面聊,主要还是在工程经验和开发习惯、带团队经验方面,没有特别具体的技术细节。

  • 一开始的技术负责人面谈,首先是自己介绍,然后切入到具体的实现细节,如期货数据与股票数据的同与异、两者之间在处理方面需要考虑的不同因素(一个是低延时,另一个需要处理吞吐量)、本地订单编号与交易所订单的匹配(本地 long64,交易所只有 5位字节)、盘中实时数据存储与推送(消息队列、共享内存、行情订阅)、交易系统在处理延时需要考虑的几个环节(tick数据从交易所推送到消息获取、进入/绕过内核通道、行情传递给策略执行等)。这个环节,感觉自己有些技术的细节掌握的不是很深入,尤其在处理网络低延时方面,被问到 socket window 时,其他之前有做个研究,但是具体的设计细节没有再深入研究。

  • 然后是 CEO 徐总面谈。他是宽德负责策略开发,但是在技术把握方面我觉得也非常不错。临走时问了一下 HR,说是最近基本上周末都在加班。。。面谈的问题涉及到交易系统设计的整体框架,行情数据如何传递给策略模块(共享内存?消息队列?如何在多进程里面实现多策略的多线程并行?)、风控模块如何实现(EMS 如何与风控模块交接?UI 的风控信号如何传递给 EMS?)。然后问了一下是否学习过操作系统内核,有点自傲的说通过 coursera 学习了一门操作系统课程,接下来便是被碾压了。问了 context switch (上下文切换在操作系统层面如何实现,我把这个和 function calling 搞混淆了)、进程的消息队列是什么机制、函数执行时如何传送参数(参数在内存是如何被放进函数里面的)、c++ 的开发环境与套件(我回答了在 Linux 操作系统进行开发,使用 CLion + Sublime + cmake + gdb,似乎这一点还算满意)、是否使用 meta programming (答否,只有基础和核心的 subset,谦虚的回答了对于 C++ 自己永远只是入门级别)。然后过渡到讨论研究生的毕业论文,聊到了利率期限结构,是解释性实证还是预测性分析,利率是否是一个 stationary process。最后常规操作,问道未来的规划,提供了两个选择:

    1. 偏管理型,设计开发框架
    2. 偏技术性,专注某个具体的领域

    本着良心,选择了第二条路线,想要在某个具体的技术领域做得极致。

  • 整体上看,今天的面谈,感觉被碾压了。很多的技术细节现在想想,其他自己把握的不是很深入,这一点徐总也说道,目前的工作岗位涉及的面太广,但是点不够深入,希望将来能够在具体的实现细节上把握的更加深入。毕竟,很多的技术,不能光说概念,而不去理解极致,有时候甚至需要自己亲手重新造轮子,这样才能体会到原开发者的用意。