Netty的ByteBuf与Protostuff序列化实现零拷贝

Cache friendly code

什么是缓存友好代码?学院派的回答大概会涉及CPU多级缓存结构等原理性的内容,但也许只要理解“缓存是CPU里一块比内存小很多,但也很多的存储空间,访问内存时CPU会一段一段的把内存读取到缓存,如果后续操作的数据在缓存里就不会读内存,整体速度就了”,这样的基本概念其实就可以开始编写缓存友好的代码了。

多核多线程时还会有False Sharing等问题,可以入门后再逐步了解。

下面是一段演示代码。对一个二维数组的数据求和,区别只是按“行”访问还是按“列”访问(会影响cache是否命中),品出其中差别也就算入门了。

在数据集小到可以全部放在cache中的时候,是真的飞起了,当然现实中很少有这样理想的情况,缓存友好的核心也就是如何设计能高命中的数据结构和代码。

常见的做法会把对象属性以线性存储,并分独立函数更新。这里会有点和面向对象拧着的感觉,需要适应一下,对外接口OOP内部组织DOP。

Unity的DOTS中的ECS(Entity Component System )是不错的概念参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <chrono>
#include <string>
#include <vector>

using namespace std;

template <int M, int N>
int sumarrayrows(int a[M][N])
{
int sum = 0;
for (int i = 0; i != M; ++i)
for (int j = 0; j != N; ++j)
sum += a[i][j];
return sum;
}

template <int M, int N>
int sumarraycols(int a[M][N])
{
int sum = 0;
for (int j = 0; j != N; ++j)
for (int i = 0; i != M; ++i)
sum += a[i][j];
return sum;
}

template <int M, int N>
void testCase()
{
cout << "testCase: " << M << "," << N << endl;
int data[M][N] = {1};
{
auto start = chrono::steady_clock::now();
int sum = 0;
for (int loop = 0; loop < 10000; loop++)
{
sum += sumarrayrows<M, N>(data);
}
auto end = chrono::steady_clock::now();
cout << "\tsum: " << sum << " time: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << endl;
}

{
auto start = chrono::steady_clock::now();
int sum = 0;
for (int loop = 0; loop < 10000; loop++)
{
sum += sumarraycols<M, N>(data);
}
auto end = chrono::steady_clock::now();
cout << "\tsum: " << sum << " time: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << endl;
}
}

int main()
{
testCase<10, 10>();
testCase<100, 100>();
}
1
clang ./cacheTest.cpp --std=c++14 -lstdc++ -O2 -o test.out
1
2
3
4
5
6
testCase: 10,10
sum: 10000 time: 5
sum: 10000 time: 306
testCase: 100,100
sum: 10000 time: 15380
sum: 10000 time: 74331

记一次ReactNative优化

这是一个试水项目,是找的代码做二次开发,在中低端手机上运行,滑动列表有点卡。

代码到手后总体浏览了一下,基本没有什么复杂计算,应该是对象创建和布局绘制占大头。

具体找到对应界面的样式和组件代码后,确实代码为了布局容易,层次嵌套比较多,样式书写简单直接,可以透明的容器都填充了颜色,经过一番调整,主要修改样式,部分扁平化层级后,问题解决。

这里不打算写具体修改的内容,原生平台的开发手册里都会有layout,overdraw和gpu profile相关的内容,里面有写的很好的指引说明。

(这里还改了react-navigation-stack里的StackViewCard,因为当时的版本,它的背景设置不了透明,本想略去的,因为本质还是改样式,但毕竟动到了三方组件里面,还是补充说明一下)


优化并不都是高深复杂的事情,很多时候认真做好基础的事情就已经能满足大部分需求。

看起来外在表现一样,可能对应的内在在代码不一样,优化在于用心。


overdraw对比 gpu对比
修改前overdraw 修改后overdraw 修改前gpu 修改后gpu

对cocos2dx-lite的JSB做针对性优化

前言

cocos2dx-lite(creator 2.0之前版本)同时支持多种js的vm,所以也就基本没有做针对特定vm的优化,项目优化时决定坚定的走V8路线,启动了针对V8的专门优化。当然这些优化都用宏定义分离开了,保障V8不可用时原有逻辑仍然不变。

具体优化条目

  • jsb绑定部分,避开se::State和se::Value直接生成V8形式的绑定,实测简单情况如 setPosition 函数效率提升50%左右。

  • 更新V8到8.0以上,开启指针压缩,降低内存 20%左右,实际项目不同会有差别,别问我怎么编译的

  • 所有源码使用v8::String::ExternalOneByteStringResource管理,减少内存copy(c++ -> v8),避免字符转码(ut8 -> utf16)消耗,实际降低内存 40M ,当然这取决于脚本文件的多少,这个项目有大量脚本。为什么是OneByteString,因为js是utf16内码,但v8有个优化处理对于纯单字节的字符串是可以用OneByteString表示的可以省一半内存,当然源码需要预处理一下,使用unicode转意表示

    1
    2
    3
    4
    5
    6
    7
    function to_latin1(str) {
    return str.replace(/[\u007f-\uffff]/g, function(ch) {
    var code = ch.charCodeAt(0).toString(16);
    while (code.length < 4) code = "0" + code;
    return "\\u" + code;
    });
    }
  • 使用v8::ArrayBuffer::Allocator使得部分ArrayBuffer可以使用宿主内存,避免宿主与VM间的copy

  • 对纹理等大内存对象通过Isolate::AdjustAmountOfExternalAllocatedMemory告知V8外部内存占用情况

  • 启用WeakRef启动参数增加--harmony-weak-refs,可以使用新标准中的WeakRef,解决动态加载的图片释放问题(项目有大量网络下载的图片)。目前会偶有崩溃,不确定是接入方式不对,还是什么原因,仍在观察中

其它

  • 通用性和效率总是需要平衡的两个方面,针对具体项目是可以做一些更激进的选择
  • V8是个好项目,但文档还不够细,读代码可以了解细节
  • 实在搞不清楚了可以参考node.js与V8结合的代码