向量化实现矩阵运算优化(一)

向量,实现,矩阵,运算,优化 · 浏览次数 : 30

小编点评

**xSIMD简介** xSIMD(SIMD Acceleration Library for C++)是一个开源的 SIMD 库,提供对常见 SIMD 指令的封装,使其操作更加简单。 **例子** 以下两个例子展示了如何使用 xSIMD 库实现两个简单的向量相加操作: ```c++ // 两个向量相加示例 void average(const std::vector& v1, const std::vector& v2, std::vector& v) { int n = v.size(); int size = xsimd::batch <double, xsimd::avx>::size; int loop = n - n % size; for (int i = 0; i < loop; i += size) { auto a = xsimd::batch <double>::load_unaligned(&v1[i]); auto b = xsimd::batch <double>::load_unaligned(&v2[i]); auto res = a + b; res.store_unaligned(&v[i]); } for (int i = loop; i < n; ++i) { v[i] = v1[i] + v2[i]; } } ``` ```c++ // 对齐内存版本示例 void average_aligned(const std::vector& s1, const std::vector& s2, std::vector& s) { int n = s.size(); int size = xsimd::batch <double>::size; int loop = n - n % size; for (int i = 0; i < loop; i += size) { auto a = xsimd::batch <double>::load_aligned(&s1[i]); auto b = xsimd::batch <double>::load_aligned(&s2[i]); auto res = a + b; res.store_aligned(&s[i]); } for (int i = loop; i < n; ++i) { s[i] = s1[i] + s2[i]; } } ``` **性能比较** 以下是 `average` 和 `average_aligned` 函数的性能比较: ``` Average: 11.5 ms Aligned: 10.5 ms ``` **结论** xSIMD 库提供了便捷的 SIMD 操作封装,可以有效提高性能。对于对齐内存的处理方式,可以使用 `average_aligned` 函数,否则可以使用 `average` 函数。

正文

xsimd简介

  xsimd是C++的一个开源simd库,实现了对常见simd指令的封装,从而使得simd的操作更为简单。接下来先从两个简单的例子来入门xsimd。

void average(const std::vector<double>& v1, const std::vector<double>& v2, std::vector<double>& v) {
    int n = v.size();
    int size = xsimd::batch<double, xsimd::avx>::size;
    int loop = n - n % size;

    for (int i = 0; i < loop; i += size) {
        auto a = xsimd::batch<double>::load_unaligned(&v1[i]);
        auto b = xsimd::batch<double>::load_unaligned(&v2[i]);
        auto res = a + b; 
        res.store_unaligned(&v[i]);
    }
    for (int i = loop; i < n; ++i) 
        v[i] = v1[i] + v2[i];
}

  上述demo实现了两个向量相加的操作,由于每次都能从vector当中加载size个数据,因此对剩余的不能进行vectorize的数据进行了分别处理。比如说,有一百个数据,每次处理8个数据,到最后剩下4个数不能凑到8,所以用朴素的迭代方式进行求和。这个demo是非对齐内存的处理方式。

using vector_type = std::vector<double, xsimd::default_allocator<double>>;
std::vector<double> v1(1000000), v2(1000000), v(1000000);
vector_type s1(1000000), s2(1000000), s(1000000);

void average_aligned(const vector_type& s1, const vector_type& s2, vector_type& s) {
    int n = s.size();
    int size = xsimd::batch<double>::size;
    int loop = n - n % size;

    for (int i = 0; i < loop; i += size) {
        auto a = xsimd::batch<double>::load_aligned(&s1[i]);
        auto b = xsimd::batch<double>::load_aligned(&s2[i]);
        auto res = a + b;
        res.store_aligned(&s[i]);
    }

    for (int i = loop; i < n; ++i) 
        s[i] = s1[i] + s2[i];
}

  要实现对齐内存的操作方式,我们必须对vector指定特定的分配器,不然最后运行出来的代码会出现segment fault。

  总之,要记住常用的api,load_aligned, store_aligned, load_unaligned, store_unaligned,它们分别对应了内存对齐与否的处理方式。接下来我们再讲解另外一个demo,并且提供与openmp的性能对比。

auto sum(const std::vector<double>&v) {
    int n = v.size();
    int size = xsimd::batch<int>::size;
    int loop = n - n % size;

    double res{};
    for (int i = 0; i < loop; ++i) {
        auto tmp = xsimd::batch<int>::load_unaligned(&v[i]);
        res += xsimd::hadd(tmp);
    }

    for (int i = loop; i < n; ++i) {
        res += v[i];
    }

    return res;
}

auto aligned_sum(const std::vector<double, xsimd::default_allocator<double>>& v) {
    int n = v.size();
    int size = xsimd::batch<int>::size;
    int loop = n - n % size;

    double res{};
    for (int i = 0; i < loop; ++i) {
        auto tmp = xsimd::batch<int>::load_aligned(&v[i]);
        res += xsimd::hadd(tmp);
    }

    for (int i = loop; i < n; ++i) {
        res += v[i];
    }
    
    return res;
}

  这个例子实现了对向量求和的功能。总体与前面基本一样,这里hadd是一个对向量求和的函数。

  对于openmp的向量化实现,则较为简单,只需要在for循环上面加上特定指令即可。不过需要注意的是,openmp支持C语法,有一些C++的新特性可能并不支持,而且需要把花括号放到下一行,我们来看具体操作。

auto parallel_sum(const std::vector<double>& v) {
    double res{};

    int n = v.size();
    #pragma omp simd
    for (int i = 0; i < n; ++i)
        res += v[i];

    return res;
}

  不要忘记加上编译选项-fopenmp和-march=native,为了性能测试,我开启了O2优化,以下是简单的测试结果,数据规模是一千万。

  一般情况下进行了内存对齐都会比没有对齐的要快一些,同时可以看到openmp与xsimd也差了一个量级。当然不同平台的结果可能会有差异,需要用更专业的工具进行测量比较。

与向量化实现矩阵运算优化(一)相似的内容:

向量化实现矩阵运算优化(一)

xsimd简介 xsimd是C++的一个开源simd库,实现了对常见simd指令的封装,从而使得simd的操作更为简单。接下来先从两个简单的例子来入门xsimd。 void average(const std::vector& v1, const std::vector

聊聊神经网络的基础知识

来自《深度学习入门:基于Python的理论与实现》 张量 Numpy、TensorFlow、Pytorch等框架主要是为了计算张量或是基于张量计算。 标量:0阶张量;12,4,3, 向量:一阶张量;[12,4,3] 矩阵:二阶张量;[ [12,4,3], [11,2,3] ] 多阶张量:多维数组;

TiDB Vector 抢先体验之用 TiDB 实现以图搜图

本文首发自 TiDB 社区专栏:https://tidb.net/blog/0c5672b9 前言 最早知道 TiDB 要支持向量化的消息应该是在23年10月份左右,到第一次见到 TiDB Vector 的样子是在今年1月初,当时 dongxu 在朋友圈发了一张图: 去年我研究了一段时间的向量数据库

NumPy 通用函数(ufunc):高性能数组运算的利器

NumPy的通用函数(ufunc)提供高性能的逐元素运算,支持向量化操作和广播机制,能应用于数组的数学、逻辑和比较运算。ufunc可提高计算速度,避免低效的循环,并允许自定义函数以满足特定需求。例如,ufunc实现加法比循环更高效。通过`frompyfunc`可创建自定义ufunc。判断函数是否为u...

[转帖]9.2 TiFlash 架构与原理

9.2 TiFlash 架构与原理 相比于行存,TiFlash 根据强 Schema 按列式存储结构化数据,借助 ClickHouse 的向量化计算引擎,带来读取和计算双重性能优势。相较于普通列存,TiFlash 则具有实时更新、分布式自动扩展、SI(Snapshot Isolation)隔离级别读

Lora训练的参数和性能

主要为了测试模型增加Lora模块后,参数量和训练速度的变化情况。结论:正常情况下,增加Lora模块是会增加参数量的,因此前向传播和反向传播的时间也会增加。但是,在大语言模型训练的情况下,因为基础模型本身参数量非常大,Lora模块增加的参数量相对非常小。并且,基础模型不参与梯度更新,可以做模型量化,实

向量数据库技术全景

本文深入探讨了向量数据库的基础概念、架构设计及实现技术,详细介绍了HNSW、FAISS和Milvus等关键算法和工具,旨在为高效管理和检索高维向量数据提供全面的技术指南。 关注TechLead,复旦博士,分享云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,复旦机

2.1 C++ STL 数组向量容器

Vector容器是C++ STL中的一个动态数组容器,可以在运行时动态地增加或减少其大小,存储相同数据类型的元素,提供了快速的随机访问和在末尾插入或删除元素的功能。该容器可以方便、灵活地代替数组,容器可以实现动态对数组扩容删除等各种复杂操作,其时间复杂度`O(l)常数阶`,其他元素的插入和删除为`O(n)线性阶`,其中n为容器的元素个数,vector具有自动的内存管理机制,对于元素的插入和删除可动

.NET周报 【5月第4期 2023-05-27】

## 国内文章 ### C#使用词嵌入向量与向量数据库为大语言模型(LLM)赋能长期记忆实现私域问答机器人落地之openai接口平替 https://www.cnblogs.com/gmmy/p/17430613.html 在上一篇[文章](https://www.cnblogs.com/gmmy/

DashVector x 通义千问大模型:打造基于专属知识的问答服务

本教程演示如何使用向量检索服务(DashVector),结合LLM大模型等能力,来打造基于垂直领域专属知识等问答服务。其中LLM大模型能力,以及文本向量生成等能力,这里基于灵积模型服务上的通义千问 API以及Embedding API来接入。 背景及实现思路 大语言模型(LLM)作为自然语言处理领域