平衡亲缘性功能以平衡的方式将连续或相邻 ID 的线程分配至相同的核心。如果期望根据英特尔 16.0 C++ 编译器文档优化线程亲缘性,在开始亲缘性时建议进行平衡。平衡的亲缘性设置仅可用于英特尔® 至强融核™ 产品系列。它并非一般 CPU 的有效选项。当利用了至强融核平台上的所有 SMT 时,平衡以及紧凑属性的效果相同。如果在至强融核平台上只利用了某些 SMT,紧凑方法将填补第一批内核上的所有 SMT 并在最后适当保留某些内核。
int iCount[nThreads] ;
.
.
.
for (some interval){
//some work . . .
iCount[myThreadId]++ // may result in false sharing
}
int iCount[nThreads*16] ;// memory padding to avoid false sharing
.
.
.
for (some interval){
//some work . . .
iCount[myThreadId*16]++ //no false sharing, unused memory
}
int iCount[nThreads] ; // make temporary local copy
.
.
.
// every thread creates its own local variable local_count
int local_Count = iCount[myThreadID] ;
for (some interval){
//some work . . .
local_Count++ ; //no false sharing
}
iCount[myThreadId] = local_Count ; //preserve values
// potential false sharing at the end,
// but outside of inner work loop much improved
// better just preserve local_Count for each thread
图 2.
相同的错误共享也可能在分配给相邻内存位置的标量上发生。这是如下面代码段所示的最后一种情况:
int data1, data2 ; // data1 and data2 may be placed in memory
//such that false sharing could occur
declspec(align(64)) int data3; // data3 and data4 will be
declspec(align(64)) int data4; // on separate cache lines,
// no false sharing
如果数据以阵列结构组织,并且软件运算所有 g 值(也可以是 r 或 b),当将高速缓存行引入高速缓存时,可能会在运算中使用整个高速缓存行。数据被更有效地载入 SIMD 寄存器,效率和性能显著改善。在许多情况下,软件开发人员用时间在实际操作中临时将数据移入要运算的阵列结构,然后根据需要将其复制回原位。在可行时,最好避免该额外的复制操作,因为这会占用执行时间。
/* Function 通过将整数转换成无符号类型,以对比参数 a 的值是否大于或等于零,还是小于参数 b 的值。参数 b 为带符号类型,通常为正值,因此它的值通常小于 0x800...其中转换负参数值可将其转换为大于 0x800 的值。 这种转换允许使用一个(而非两个)条件。 */
inline bool is_a_ge_zero_and_a_lt_b(int a, int b) {
return static_cast<unsigned>(a) < static_cast<unsigned>(b);
}
在 BVLC Caffe 中,原始代码包含条件校验 if (x >= 0 && x < N),其中 x和 N为带符号整数,N通常为正值。 将这些整数类型转换成无符号整数,可改变比对间隔。 无需通过逻辑 AND 运行两次比对,进行类型转换后,只需进行一次比对:
template <typename Dtype>
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& \
bottom, const vector<Blob<Dtype>*>& top) {
const Dtype* weight = this->blobs_[0]->cpu_data();
// If we have more threads available than batches to be prcessed then
// we are wasting resources (lower batches than 36 on XeonE5)
// So we instruct MKL
for (int i = 0; i < bottom.size(); ++i) {
const Dtype* bottom_data = bottom[i]->cpu_data();
Dtype* top_data = top[i]->mutable_cpu_data();
#ifdef _OPENMP
#pragma omp parallel for num_threads(this->num_of_threads_)
#endif
for (int n = 0; n < this->num_; ++n) {
this->forward_cpu_gemm(bottom_data + n*this->bottom_dim_,
weight,
top_data + n*this->top_dim_);
if (this->bias_term_) {
const Dtype* bias = this->blobs_[1]->cpu_data();
this->forward_cpu_bias(top_data + n * this->top_dim_, bias);
}
}
}
}
Python 2.7
tar -xvzf l_python27_p_2017.0.028.tgz
cd l_python27_p_2017.0.028
./install.sh
Python 3.5
tar -xvzf l_python35_p_2017.0.028.tgz
cd l_python35_p_2017.0.028
./install.sh
Python 2.7 and Python 3.5
git clone https://github.com/Lasagne/Lasagne.git
cd Lasagne
python setup.py build
python setup.py install
训练
cd Lasagne/examples
python mnist.py [model [epochs]]
-- where model can be mlp - simple multi layer perceptron (default) or
cnn - simple convolution neural network.
and epochs = 500 (default)
cat theano/democase/alexnet_grp1/preprocessing/paths.yaml
train_img_dir: '/mnt/DATA2/TEST/ILSVRC2012_img_train/'
# the dir that contains folders like n01440764, n01443537, ...
val_img_dir: '/mnt/DATA2/TEST/ILSVRC2012_img_val/'
# the dir that contains ILSVRC2012_val_00000001~50000.JPEG
tar_root_dir: '/mnt/DATA2/TEST/parsed_data_toy' # dir to store all the preprocessed files
tar_train_dir: '/mnt/DATA2/TEST/parsed_data_toy/train_hkl' # dir to store training batches
tar_val_dir: '/mnt/DATA2/TEST/parsed_data_toy/val_hkl' # dir to store validation batches
misc_dir: '/mnt/DATA2/TEST/parsed_data_toy/misc'
# dir to store img_mean.npy, shuffled_train_filenames.npy, train.txt, val.txt
meta_clsloc_mat: '/mnt/DATA2/imageNet-2012-images/ILSVRC2014_devkit/data/meta_clsloc.mat'
val_label_file: '/mnt/DATA2/imageNet-2012-images/ILSVRC2014_devkit/data/ILSVRC2014_clsloc_validation_ground_truth.txt'
# although from ILSVRC2014, these 2 files still work for ILSVRC2012
# caffe style train and validation labels
valtxt_filename: '/mnt/DATA2/TEST/parsed_data_toy/misc/val.txt'
traintxt_filename: '/mnt/DATA2/TEST/parsed_data_toy/misc/train.txt'
cd theano/democase/alexnet_grp1/preprocessing
chmod u+x make_hkl.py make_labels.py make_train_val_txt.py
./generate_toy_data.sh
在 CPU 上训练 AlexNet
修改 config.yaml 文件,以更新预处理数据集的路径:
cd theano/democase/alexnet_grp1/
# Sample changes to the path for input(label_folder, mean_file) and output(weights_dir)
label_folder: /mnt/DATA2/TEST/parsed_data_toy/labels/
mean_file: /mnt/DATA2/TEST/parsed_data_toy/misc/img_mean.npy
weights_dir: ./weight/ # directory for saving weights and results
python make_hkl.py toy
generating toy dataset ...
Traceback (most recent call last):
File "make_hkl.py", line 293, in <module>
train_batchs_per_core)
ValueError: xrange() arg 3 must not be zero
tar -xvzf scipy-0.16.1.tar.gz (can be downloaded from: https://sourceforge.net/projects/scipy/files/scipy/0.16.1/ or
obtain the latest sources from https://github.com/scipy/scipy/releases)
cd scipy-0.16.1/
python setup.py config --compiler=intelem --fcompiler=intelem build_clib --compiler=intelem --fcompiler=intelem build_ext --compiler=intelem --fcompiler=intelem install --user
附录B
利用源代码构建、安装性能指标评测的关联组件
DBN-Kyoto
//Untar and install all the provided tools:
cd theano/democase/DBN-Kyoto/tools
tar -xvzf Imaging-1.1.7.tar.gz
cd Imaging-1.1.7
python setup.py build
python setup.py install --user
cd theano/democase/DBN-Kyoto/tools
tar -xvzf python-dateutil-2.4.1.tar.gz
cd python-dateutil-2.4.1
python setup.py build
python setup.py install --user
cd theano/democase/DBN-Kyoto/tools
tar -xvzf pytz-2014.10.tar.gz
cd pytz-2014.10
python setup.py build
python setup.py install --user
cd theano/democase/DBN-Kyoto/tools
tar -xvzf pandas-0.15.2.tar.gz
cd pandas-0.15.2
python setup.py build
python setup.py install --user
INT Supports_Feature()
{
; result returned in eax
mov eax, 1
cpuid
and ecx, CONSTANT
cmp ecx, CONSTANT; check desired feature flags
jne not_supported
; processor supports features
mov ecx, 0; specify 0 for XFEATURE_ENABLED_MASK register
XGETBV; result in EDX:EAX
and eax, 06H
cmp eax, 06H; check OS has enabled both XMM and YMM state support
jne not_supported
mov eax, 1; mark as supported
jmp done
NOT_SUPPORTED:
mov eax, 0 ; // mark as not supported
done:
}
用途
在最低的编程级别,大部分常用 x86 汇编器现在都支持英特尔® AVX、FMA、AES 和 VPCLMULQDQ 指令,包括 Microsoft MASM*(Microsoft Visual Studio* 2010 版本)、NASM*、FASM* 和 YASM*。请参阅各自的相关文档,获取详细信息。
对于语言编译器,英特尔® C++ 编译器 11.1 版及更高版本和英特尔® Fortran 编译器都可以通过编译器开关支持英特尔® AVX;而且这两种编译器还支持自动矢量化和浮点循环。英特尔® C++ 编译器支持英特尔® AVX 内联函数(使用 #include 访问内联函数)和内嵌汇编语言,甚至还可以使用 #include "avxintrin_emu.h"支持英特尔® 内联函数模拟。
Microsoft Visual Studio* C++ 2010 (SP1) 及更高版本在编译 64 位置代码(使用 /arch:AVX编译器开关)时支持英特尔® AVX(请参阅“更多信息”)。它使用 标头支持内联函数,但是不支持内嵌汇编语言。MASM*、代码的反汇编视图和寄存器的配置程序视图(完全支持 YMM)中都支持英特尔® AVX。
GNU Compiler Collection* (GCC*) 4.4 版通过同一标头 支持英特尔® AVX 内联函数。Binutils 2.20.51.0.1 及更高版本、gdb 6.8.50.20090915 及更高版本、最新版的 GNU 汇编器 (GAS) 以及 objdump 中还提供了其他 GNU 工具链支持。如果您的编译器不支持英特尔® AVX,您可以在许多情况下发出所需的字节,但是一流的支持能为您带来更多方便。
以上提及的三个 C++ 编译器都可以从 C 或 C++ 代码中使用英特尔® AVX 支持同一内联函数运算,从而简化运算。内联函数是编译器使用相应的汇编函数替换的函数。大部分英特尔® AVX 内联函数的命名都遵循以下格式:
z,p are complex numbers
for each point p on the complex plane
z = 0
for count = 0 to max_iterations
if abs(z) > 2.0
break
z = z*z+p
set color at p based on count reached
Chris Lomont 是 Cybernet Systems 的一名研发工程师,曾参与过量子计算算法、NASA 图像处理、美国国土安全部门安全软件的开发和计算机取证分析等项目。他拥有普渡大学的博士学位,此外还拥有物理学、数学和计算机科学的学士学位。作为一名游戏程序员,他还短期从事过金融建模、机器人制造和各种咨询工作。他在业余时间喜欢与妻子一起徒步旅行,还爱好看电影、做专题座谈、娱乐性编程、数学研究、研究物理、听音乐和做各种实验。您可以访问他的个人网站:www.lomont.org或者他的电子设备网站:http://www.hypnocube.com/
Repeat the following procedure 64 times:
for each MPI process n from 0 to 7:
for each element j in the array A[k]:An[j] ← 0.5*An[j] + 0.25*Aprevious[j] + 0.25*Anext[j]
为了划分工作,我们应首先考虑 GPU 如何划分工作,因为这可能同样适用于 CPU。计算着色器通过两种方式控制工作的划分。首先是调度大小,指的是录制命令流时传输至 API 调用的大小。描述了运行的工作组的数量和维度。第二个是本地工作组的数量和维度,该工作组被硬编码为着色器。本地工作组的每个项目可视作一个工作线程,如果使用了共享内存,每个线程可与工作组中的其他线程共享信息。
通过观察 nBodyGravityCS.hlsl计算着色器发现,本地工作组的规模为 128 x 1 x 1,并使用共享内存优化某些粒子负载,但是在 CPU 上没有必要这样做。除此以外,线程之间没有交互,每个线程与外层循环执行不同的粒子,与内层循环上的所有其它粒子交互。
这应该非常适合 CPU 矢量宽度,因此我们使用面向英特尔 AVX2 的 8 x 1 x 1 或面向英特尔 SSE4 的 4 x 1 x 1 更换 128 x 1 x 1。我们也可以将调度大小用作多线程代码的提示,根据 SIMD 宽度,将 10,000 个粒子除以 8 或 4。但是,由于我们已经发现每个线程之间没有关联,可以简单地将粒子数量除以线程池中的可用线程数量,或除以设备上的可用逻辑内核数量,启用了英特尔® 超线程技术的典型 4 核 CPU 上的内核数量为 8。移植其他计算着色器时,可能需要考虑更多因素。
这为我们提供了以下伪代码:
For each thread
Process N particles where N is 10000/threadCount
For each M particles from N, where M is the SIMD width
Test interaction with all 10000 particles
移植 HLSL* 至标量 C
编写英特尔 SPMD 程序编译器内核时,除非您有丰富的经验,否则建议您首先编写一个标量 C 版本。这将确保所有应用粘接、多线程和内存操作在开始矢量化之前已经运行。
为此,nBodyGravityCS.hlsl中的多数 HLSL 代码可在 C 中工作,除了为粒子添加外层循环和将着色器数学矢量类型改为使用基于 C 的类型以外,只需极少的修改。在本示例中,float4/float3 类型与 DirectX XMFLOAT4/XMFLOAT3 类型交换,某些矢量数学操作被划分为标量操作。
CPU 粒子缓冲被用于读写,如上所述,借助用于同步的原始栅栏将写入缓冲上传至 GPU。为了实现线程化,示例使用了并行模式库中的 Microsoft’s concurrency::parallel_for结构。
此时,简要介绍数据布局非常重要。在 CPU 上使用矢量寄存器时,高效加载和卸载寄存器非常重要;不这样做将导致性能大幅下降。为此,矢量寄存器需要从阵列结构 (SoA) 数据来源中加载数据,因此,可以使用单条指令将相邻内存值的矢量宽度直接加载至运行中的矢量寄存器。如果无法实现,需要降低收集操作的速度,以加载非相邻值的矢量宽度至矢量寄存器,需要通过分散操作再次保存数据。