Filecoin优化SDR的版本介绍

Stella981
• 阅读 845

在AMD3970x上,P1的性能2小时10分钟。看了看官方的优化思路,分享一下。P1性能优化的核心Patch如下:

  1. commit 0313c663c1b4c7ea891dcaf7245e2cc5eb4b1f81

  2. Author: dignifiedquire <me@dignifiedquire.com>

  3. Date: Thu Aug 27 23:08:12 2020 +0200

  4. Optimize Phase1.

这个Patch在8月27号就做出来了。优化思路比较清晰:通过预读取base/exp parent的数据,让数据的准备和sha256的计算并行。在介绍具体的优化逻辑之前,介绍一下AMD CPU架构的小知识:

1. Core Complex

CCX是AMD CPU架构的一个术语。CCX - CPU Core Complex。CCX指的是一组(4个)CPU核心和缓存(L1/L2/L3)。4个CCX一组,也有一个专门的术语 - CCD (Core Chiplet Die)。AMD的Ryzen 3000系列的处理器都采用类似的架构。

Filecoin优化SDR的版本介绍

上图给一个示例。整个芯片由两个die组成(上面是die0,下面是die1)。每个Die(CCD)包括两个CCX。注意的是,每个core都有自己的L1/L2缓存,但是L3缓存是几个Core共享的。也就是说,在CCX的四个核可以共享L3缓存,如果四个核需要访问同一段数据,可以加速数据的访问时间。

2. SDR算法优化思路

区块链行情,就是将exp/base的parent节点信息的获取和sha256的计算并行。在进行节点计算之前,预先读取exp/base的节点信息并保存在sdr_parent_cache环缓冲中。注意,sdr_parent_cache和parent_cache不一样。sdr_parent_cache是某个节点依赖的多个节点的信息,parent_cache是节点依赖信息的缓存。Filecoin官方称这个优化是Multi-core优化,有几个原因:1/ 节点依赖数据的预读取和节点的计算由多核实现。2/ 预读取的逻辑本身也是由多核实现。

3. SDR算法优化逻辑分析

明确了优化思路,SDR算法优化的代码还是比较容易理解的。核心逻辑在storage-proofs/porep/src/stacked/vanilla/create_label/mult.rs代码的create_layer_labels函数中。

  1. fn create_layer_labels(
  2. parents_cache: &CacheReader,
  3. replica_id: &[u8],
  4. layer_labels: &mut MmapMut,
  5. exp_labels: Option<&mut MmapMut>,
  6. num_nodes: u64,
  7. cur_layer: u32,
  8. ) -> Result<()> {

parents_cache是节点依赖关系的cache数据,replica_id是replia的编号信息,layer_labels和exp_labels分别是当前层和上一层的数据,num_nodes节点个数,cur_layer是当前层的编号。

逻辑的实现采用“生产者/消费者“模式,生产者”生成“节点依赖数据,消费者获取这些数据并进行sha256的计算。

3.1 确定生产者配置

生产者,通过多线程预读取某个节点依赖的节点数据。lookahead指预读取最大的节点个数。num_producers是使用多少个生产者。producer_stride是每个生产者一次生产多少个节点的依赖数据。

  1. let (lookahead, num_producers, producer_stride) = if cur_layer == 1 {
  2. (400, 1, 16)
  3. } else {
  4. (800, 2, 128)
  5. };

在Layer为1时,采用1个生产者,每个生产者一次生产16个节点的依赖数据。其他Layer时,采用2个生产者,每个生产者一次生产128个节点的依赖数据。Layer为1和其他时候区分开,是因为Layer1的依赖关系和其他层不一样。

3.2 申请Ring缓存

生产者预生成的节点依赖数据是通过Ring缓存(ring_buf)存储。这个存储在生产者和消费者之间共享。

  1. const BYTES_PER_NODE: usize = (NODE_SIZE * DEGREE) + 64;

  2. let mut ring_buf = RingBuf::new(BYTES_PER_NODE, lookahead);

  3. let mut base_parent_missing = vec![BitMask::default(); lookahead];

  4. // Fill in the fixed portion of all buffers

  5. for buf in ring_buf.iter_slot_mut() {

  6. prepare_block(replica_id, cur_layer, buf);

  7. }

需要解释一下base_parent_missing。因为base parent的节点需要当前层前面节点的计算结果,在预读取的时候,可能这些节点还没有计算出来。这些节点的信息通过base_parent_missing标示出来。注意,ring_buf中的每个节点的数据除了依赖的节点信息外,还有一些辅助信息(replica_id, node_id等等)。这些辅助信息的长度是64,所以BYTES_PER_NODE还会加上64。prepare_block函数就是预先填充辅助信息。

3.3 申请原子锁操作

生产者和消费者之间需要同步。多个生产者之间也需要同步。

  1. // Node the consumer is currently working on
  2. let cur_consumer = AtomicU64::new(0);
  3. // Highest node that is ready from the producer
  4. let cur_producer = AtomicU64::new(0);
  5. // Next node to be filled
  6. let cur_awaiting = AtomicU64::new(1);

cur_consumer和cur_producer就是用于生产者和消费者之间的同步。cur_awaiting用于多个生产者之间的同步。

3.4 启动生产者

设定了多少生产者,就启动个多少线程。每个线程调用create_label_runner函数,实现了具体的预读取逻辑。

  1. for _i in 0..num_producers {

  2. let layer_labels = &layer_labels;

  3. let exp_labels = exp_labels.as_ref();

  4. let cur_consumer = &cur_consumer;

  5. let cur_producer = &cur_producer;

  6. let cur_awaiting = &cur_awaiting;

  7. let ring_buf = &ring_buf;

  8. let base_parent_missing = &base_parent_missing;

  9. runners.push(s.spawn(move |_| {

  10. create_label_runner(

  11. parents_cache,

  12. layer_labels,

  13. exp_labels,

  14. num_nodes,

  15. cur_consumer,

  16. cur_producer,

  17. cur_awaiting,

  18. producer_stride,

  19. lookahead as u64,

  20. ring_buf,

  21. base_parent_missing,

  22. )

  23. }));

  24. }

具体的逻辑比较清晰易懂,感兴趣的小伙伴直接看源代码。

3.5 启动消费者

消费者的逻辑相对多一些。每一层的第一个节点因为没有base parent节点,计算比较特殊,单独处理:

  1. let mut buf = [0u8; (NODE_SIZE * DEGREE) + 64];

  2. prepare_block(replica_id, cur_layer, &mut buf);

  3. cur_node_ptr[..8].copy_from_slice(&SHA256_INITIAL_DIGEST);

  4. compress256!(cur_node_ptr, buf, 2);

  5. // Fix endianess

  6. cur_node_ptr[..8].iter_mut().for_each(|x| *x = x.to_be());

  7. cur_node_ptr[7] &= 0x3FFF_FFFF; // Strip last two bits to ensure in Fr

查看生产者提供了多少节点数据,并开始进行处理:

  1. let ready_count = producer_val - i + 1;
  2. for _count in 0..ready_count {
  3. cur_node_ptr = &mut cur_node_ptr[8..];
  4. // Grab the current slot of the ring_buf
  5. let buf = unsafe { ring_buf.slot_mut(cur_slot) };

处理过程分为两部分。第一部分,base parent有可能有缺失,先补充缺失的数据:

  1. for k in 0..BASE_DEGREE {

  2. let bpm = unsafe { base_parent_missing.get(cur_slot) };

  3. if bpm.get(k) {

  4. // info!("getting missing parent, k={}", k);

  5. let source = unsafe {

  6. if cur_parent_ptr.is_empty() {

  7. cur_parent_ptr =

  8. parents_cache.consumer_slice_at(cur_parent_ptr_offset);

  9. }

  10. // info!("after unsafe, when getting miss parent");

  11. let start = cur_parent_ptr[0] as usize * NODE_WORDS;

  12. let end = start + NODE_WORDS;

  13. // info!("before as_slice(), when getting miss parent");

  14. &layer_labels.as_slice()[start..end]

  15. };

  16. buf[64 + (NODE_SIZE * k)..64 + (NODE_SIZE * (k + 1))]

  17. .copy_from_slice(source.as_byte_slice());

  18. // info!("got missing parent, k={}", k);

  19. }

  20. cur_parent_ptr = &cur_parent_ptr[1..];

  21. cur_parent_ptr_offset += 1;

  22. }

第二部分,在base parent数据补充完整后,所有的数据都已经准备完成,进行sha256的计算:

  1. if cur_layer == 1 {

  2. // Six rounds of all base parents

  3. for _j in 0..6 {

  4. compress256!(cur_node_ptr, &buf[64..], 3);

  5. }

  6. // round 7 is only first parent

  7. memset(&mut buf[96..128], 0); // Zero out upper half of last block

  8. buf[96] = 0x80; // Padding

  9. buf[126] = 0x27; // Length (0x2700 = 9984 bits -> 1248 bytes)

  10. compress256!(cur_node_ptr, &buf[64..], 1);

  11. } else {

  12. // Two rounds of all parents

  13. let blocks = [

  14. *GenericArray::<u8, U64>::from_slice(&buf[64..128]),

  15. *GenericArray::<u8, U64>::from_slice(&buf[128..192]),

  16. *GenericArray::<u8, U64>::from_slice(&buf[192..256]),

  17. *GenericArray::<u8, U64>::from_slice(&buf[256..320]),

  18. *GenericArray::<u8, U64>::from_slice(&buf[320..384]),

  19. *GenericArray::<u8, U64>::from_slice(&buf[384..448]),

  20. *GenericArray::<u8, U64>::from_slice(&buf[448..512]),

  21. ];

  22. sha2::compress256((&mut cur_node_ptr[..8]).try_into().unwrap(), &blocks);

  23. sha2::compress256((&mut cur_node_ptr[..8]).try_into().unwrap(), &blocks);

  24. // Final round is only nine parents

  25. memset(&mut buf[352..384], 0); // Zero out upper half of last block

  26. buf[352] = 0x80; // Padding

  27. buf[382] = 0x27; // Length (0x2700 = 9984 bits -> 1248 bytes)

  28. compress256!(cur_node_ptr, &buf[64..], 5);

  29. }

计算分为两种情况,一种是第一层的数据,一种是其他层的数据。计算sha256的过程稍稍有些不同。至此,整个优化算法逻辑就完整了。

总结:

Filecoin官方宣布了SDR的优化版本。在AMD3970x上,P1的性能2小时10分钟。优化思路比较清晰,通过预读取base/exp parent的数据,让数据的准备和sha256的计算并行。

原创,https://www.biyungu.com/news/2718.html

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这