矢量运算
概述:
矢量运算是一种基于向量的数学运算,它遵循特定的法则。以下是矢量运算的一些基本原理:
- 矢量加法:可以使用平行四边形法则或三角形法则来执行。当两个矢量相加时,可以将它们的起点放在同一个点上,然后根据平行四边形法则,从这两个矢量的尾部画两条线,使其首尾相连,形成一个平行四边形。这个平行四边形的对角线即表示两矢量之和。如果使用三角形法则,则是将一个矢量的尾部与另一个矢量的头部相连,形成三角形,再从第一个矢量的起点到第二个矢量的尾部画一条线,这条线代表两矢量之和。
- 矢量减法:被视作矢量加法的逆运算。从一个矢量中减去另一个矢量相当于加上那个矢量的负矢量。
- 矢量与标量乘法:将矢量的每个分量乘以标量即可。几何意义上,一个矢量乘以正标量会使得矢量在保持方向不变的情况下扩大若干倍,而乘以负标量则会使矢量的方向反转并等比例缩小。
- 矢量共线定理:表明如果两个矢量共线,那么其中一个矢量可以表示为另一个矢量与一个标量的乘积。
矢量运算比标量快的原因
-
并行性(Parallelism):矢量化操作可以同时对多个数据元素执行相同的操作。现代处理器中的矢量处理单元(如SSE、AVX指令集)可以在单个指令中对多个数据元素执行相同的操作,从而实现并行计算。相比之下,标量操作则需要逐个处理每个数据元素,无法充分利用处理器的并行计算能力。
-
数据局部性(Data Locality):矢量化操作可以提高数据的局部性,减少内存访问次数。由于矢量操作一次处理多个数据元素,可以更有效地利用处理器的缓存,降低了内存访问的开销。而标量操作需要对每个数据元素分别进行内存访问,导致更多的缓存未命中和内存带宽瓶颈。
-
指令优化(Instruction Optimization):矢量指令集(如SSE、AVX)提供了丰富的指令,能够对矢量数据进行高效的操作,包括加法、乘法、逻辑运算等。处理器可以利用这些指令进行更有效的优化,从而提高运算速度。相比之下,标量指令集的操作对象是单个数据元素,限制了处理器进行指令级并行优化的能力。
-
数据重用(Data Reuse):矢量化操作可以更好地利用数据重用的机会。在循环等迭代过程中,矢量化操作可以将多次计算相同的操作合并为一次计算,从而减少了重复计算的开销。而标量操作需要对每个数据元素分别进行计算,无法充分利用数据重用的机会。
C++矢量运算与标量运算的比较
#include <iostream>
#include <vector>
#include <chrono>
int main() {
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
std::vector<int> result(3);
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < vec1.size(); ++i) {
result[i] = vec1[i] + vec2[i];
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << "Vector addition time: " << elapsed.count() << " seconds" << std::endl;
int scalar1 = 1;
int scalar2 = 4;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 3; ++i) {
int scalar_result = scalar1 + scalar2;
}
end = std::chrono::high_resolution_clock::now();
elapsed = end - start;
std::cout << "Scalar addition time: " << elapsed.count() << " seconds" << std::endl;
return 0;
}
运行结果:
java矢量运算与标量运算的比较
package org.cyl.spaceutils;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
public class VectorScalarOperations {
public static void main(String[] args) {
// 定义矢量a和b
float[] a = {1, 2, 3};
float[] b = {4, 5, 6};
float[] c=new float[a.length];
VectorScalarOperations v1=new VectorScalarOperations();
long startTime = System.nanoTime();
v1.vectorComputation(a,b,c);
long endTime = System.nanoTime();
System.out.println("矢量花费的时间:"+(endTime-startTime));
long startTime1 = System.nanoTime();
v1.xadd(a,b,c);
long endTime1 = System.nanoTime();
System.out.println("标量花费的时间:"+(endTime1-startTime1));
}
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb));
vc.intoArray(c, i);
}
}
void xadd(float[]a,float[]b,float[]c){
for (int i=0;i<a.length;i++){
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
}
java的要大量数据才能展现出其价值。