# 借助 SIMD 数据布局模板和数据预处理提高 SIMD 在动画中的使用效率

## 背景知识和问题陈述

```Algorithm Version
#0: Original
#1: SIMD
#2: SIMD, data sorting
#3: SIMD, data sorting, SDLT container
#4: SIMD, data sorting, SDLT container, sdlt::uniform_soa_over_1d```

## 版本 0： 算法

```typedef std::vector<Attachment> AttachmentsArray;
AttachmentsArray mAttachments;

void
computeAffectedPositions(std::vector<Vec3d>& aAffectedPositions)
{
const int count = mAttachments.size();
#pragma novector
for (unsigned int i=0u; i < count; ++i) {
Attachment a = mAttachments[i];

// Compute affected position
// NOTE: Requires many gathers (indirect accesses)
Vec3d deformed0 = a.offset * mJoints[a.index0].xform * a.weight0;
Vec3d deformed1 = a.offset * mJoints[a.index1].xform * a.weight1;
Vec3d deformed2 = a.offset * mJoints[a.index2].xform * a.weight2;
Vec3d deformed3 = a.offset * mJoints[a.index3].xform * a.weight3;

Vec3d deformedPos = deformed0 + deformed1 + deformed2 + deformed3;

// Scatter result
aAffectedPositions[i] = deformedPos;
}
}```

## 版本 1： SIMD

`#pragma omp simd`

## 版本 2（第 1 部分）： 通过整理对数据进行预处理以确保数据统一

```void
computeAffectedPositions(std::vector<Vec3d>& aAffectedPositions)
{
// Here we have a "sorted" array of Attachments, and an array of IndiceSets.
// Each IndiceSet specifies the range of Attachment-indexes that share common
// set of Joint-indexes. So we loop over the IndiceSets (outer loop), and
// loop over the Attachments over the range (inner SIMD loop).
const int setCount = static_cast<int>(mIndiceSetArray.size());
for (int setIndex = 0; setIndex < setCount; ++setIndex) {
const auto & indiceSet = mIndiceSetArray[setIndex];
const int startAt = indiceSet.rangeStartAt;
const int endBefore = indiceSet.rangeEndBefore;

// Uniform (loop-invariant) data, hoisted outside inner loop
// NOTE: Avoids indirection, therefore gathers
const Joint joint0 = mJoints[indiceSet.index0];
const Joint joint1 = mJoints[indiceSet.index1];
const Joint joint2 = mJoints[indiceSet.index2];
const Joint joint3 = mJoints[indiceSet.index3];

#pragma omp simd
for (int i = startAt; i < endBefore; ++i) {
const Attachment a = mAttachmentsSorted[i];

// Compute an affected position
const Vec3d deformed0 = a.offset * joint0.xform * a.weight0;
const Vec3d deformed1 = a.offset * joint1.xform * a.weight1;
const Vec3d deformed2 = a.offset * joint2.xform * a.weight2;
const Vec3d deformed3 = a.offset * joint3.xform * a.weight3;

const Vec3d deformedPos = deformed0 + deformed1 + deformed2 + deformed3;

// Scatter result
aAffectedPositions[a.workIdIndex] = deformedPos;
}
}
}
```

## 版本 3： SDLT 容器

```// typedef sdlt::soa1d_container<AttachmentSorted> AttachmentsSdltContainer;
typedef sdlt::asa1d_container<AttachmentSorted, sdlt::simd_traits<double>::lane_count>    AttachmentsSdltContainer;
AttachmentsSdltContainer mAttachmentsSdlt;

void
computeAffectedPositions(std::vector<Vec3>& aAffectedPositions)
{
// SDLT access for inputs
auto sdltInputs = mAttachmentsSdlt->const_access();

math::Vec3* affectedPos = &aAffectedPositions[0];
for (int setIndex=0; setIndex < setCount; ++setIndex) {
// . . .

// SIMD inner loop
// The ‘sdlt::asa1d_container’ needs a compound index that identifies the AOS index as
// well as the SOA lane index, and the macro SDLT_SIMD_LOOP provides a compatible index
// over ranges that begin/end on SIMD lane count boundaries (because we padded our data).
// NOTE: sdlt::asa_container and SDLT_SIMD_LOOP are “Preview” features in ICC 16.2, SDLT v2.
SDLT_SIMD_LOOP_BEGIN(index, startAt, endBefore, sdlt::simd_traits<double>::lane_count)
{
const AttachmentSorted a = sdltInputs[index];

// . . .

affectedPos[a.workIdIndex] = deformedPos;
}
SDLT_SIMD_LOOP_END
}
}
```

## 参考资料

1. 下载包含源代码的示例：
https://software.intel.com/sites/default/files/managed/de/3f/animation-simd-sdlt-whitepaper.tar.gz
2. SDLT 文档（包含部分代码示例）：
https://software.intel.com/zh-cn/code-samples/intel-compiler/intel-compiler-features/intel-sdlt
3. SIGGRAPH 2015： DreamWorks 动画 (DWA)： 如何借助 SIMD 将皮肤变形性能提升 4 倍：
http://www.slideshare.net/IntelSoftware/dreamwork-animation-dwa
4. “先试后买”英特尔® Parallel Studio XE 评估版：
5. 面向合格学生、教育工作者、学术研究人员和开源工作者的英特尔® Parallel Studio XE 免费版本：
https://software.intel.com/zh-cn/qualify-for-free-software
6. 英特尔® VTune™ Amplifier 2016：
https://software.intel.com/zh-cn/intel-vtune-amplifier-xe

## 脚注

1 单指令多数据 (SIMD) 指利用数据层并行化，单条指令同时处理多个数据。 它与传统“标量操作”（使用单条指令处理单个数据）正好相反。

2 矢量化指计算机程序从标量实施转化为矢量（或 SIMD）实施。

3 pragma simdhttps://software.intel.com/zh-cn/node/583427pragma omp simdhttps://software.intel.com/zh-cn/node/583456

4 非单位步长内存访问指在循环连续增量的过程中，从内存的非相邻位置访问数据。 这样会严重影响性能。 相反，以单位步长（或顺序）形式访问内存会显著提高效率。

6 SDLT 基元可限制对象，有助于编译器成功完成 SIMD 循环中针对局部变量的私有化，即每个 SIMD 通道都有一个私有变量实例。 为满足这一标准，对象必须是 Plain Old Data (POD)，拥有行内对象成员，没有嵌套阵列，而且没有虚拟函数。

7 在矢量化过程中，开发人员应该试验各种不同的编译指示（比如 simd、omp simd、ivdep 和矢量 {always [assert]})，并使用 Opt-报告。

8 我们为基于 Linux* 的英特尔® C++ 编译器 16.0 (2016) 添加了命令行选项“-qopt-report=5 –qopt-report-phase=vec”，以生成 Opt-报告 (*.optrpt)。

9 使用英特尔® C++ 编译器 16.0 生成英特尔® 高级矢量扩展（英特尔® AVX) 指令时，可将选项 “-xAVX” 添加至编译命令行。

10AVX512 指令集包含传播负载指令，可降低迭代开始之前准备统一数据时所产生的 SIMD 开销。

i 在性能检测过程中涉及的软件及其性能只有在英特尔微处理器的架构下方能得到优化。

15.61 KB