ARM NEON編程系列1-導論


ARM NEON 編程系列1 - 導論

前言

本系列博文用於介紹ARM CPU下NEON指令優化。

  • 博文github地址:github
  • 相關代碼github地址:github

NEON歷史

ARM處理器的歷史可以閱讀文獻[2],本文假設讀者已有基本的ARM CPU下編程的經驗,本文面向需要了解ARM平台下通過NEON進行算法優化的場景。
ARM CPU最開始只有普通的寄存器,可以進行基本數據類型的基本運算。自ARMv5開始引入了VFP(Vector Floating Point)指令,該指令用於向量化加速浮點運算。自ARMv7開始正式引入NEON指令,NEON性能遠超VFP,因此VFP指令被廢棄。

NEON用途

類似於Intel CPU下的MMX/SSE/AVX/FMA指令,ARM CPU的NEON指令同樣是通過向量化計算來進行速度優化,通常應用於圖像處理、音視頻處理等等需要大量計算的場景。

Hello world

下面給一個最基本的例子來說明NEON的作用:
注意:

  • 代碼采用C++11編寫,后續博客代碼均以C++11編寫,不再重述)
  • 此系列博客采用neon2sse.h將NEON指令翻譯成SSE指令以使得代碼可以在x86/x64 CPU上運行。本文所有代碼均在windows vs2013以及android-ndk-r11c下編譯測試通過。

完整代碼地址:基本NEON優化示例代碼

//填充隨機數
static void fill_random_value(std::vector<float>& vec_data)
{
	std::uniform_real_distribution<float> distribution(
		std::numeric_limits<float>::min(),
		std::numeric_limits<float>::max());
	std::default_random_engine generator;

	std::generate(vec_data.begin(), vec_data.end(), [&]() { return distribution(generator); });
}
//判斷兩個vector是否相等
static bool is_equals_vector(const std::vector<float>& vec_a,
  const std::vector<float>& vec_b)
{
	if (vec_a.size() != vec_b.size())
	{
		return false;
	}
	for (size_t i = 0; i < vec_a.size(); i++)
	{
		if (vec_a[i] != vec_b[i])
		{
			return false;
		}
	}
	return true;
}
//正常的vector相乘 (注意:需要關閉編譯器的自動向量化優化)
static void normal_vector_mul(const std::vector<float>& vec_a,
  const std::vector<float>& vec_b,
  std::vector<float>& vec_result)
{
	assert(vec_a.size() == vec_b.size());
	assert(vec_a.size() == vec_result.size());
	//compiler may optimized auto tree vectorize (test this diabled -ftree-vectorize)
	for (size_t i = 0; i < vec_result.size();i++)
	{
		vec_result[i] = vec_a[i] * vec_b[i];
	}
}
//NRON優化的vector相乘
static void neon_vector_mul(const std::vector<float>& vec_a,
  const std::vector<float>& vec_b,
  std::vector<float>& vec_result)
{
	assert(vec_a.size() == vec_b.size());
	assert(vec_a.size() == vec_result.size());
	int i = 0;
	//neon process
	for (; i < (int)vec_result.size() - 3 ; i+=4)
	{
		const auto data_a = vld1q_f32(&vec_a[i]);
		const auto data_b = vld1q_f32(&vec_b[i]);
		float* dst_ptr = &vec_result[i];
		const auto data_res = vmulq_f32(data_a, data_b);
		vst1q_f32(dst_ptr, data_res);
	}
	//normal process
	for (; i < (int)vec_result.size(); i++)
	{
		vec_result[i] = vec_a[i] * vec_b[i];
	}
}
//測試函數
//FuncCostTimeHelper是一個計算時間消耗的helper類
static int test_neon()
{
	const int test_round = 1000;
	const int data_len = 10000;
	std::vector<float> vec_a(data_len);
	std::vector<float> vec_b(data_len);
	std::vector<float> vec_result(data_len);
	std::vector<float> vec_result2(data_len);
	//fill random value in vecA & vecB
	fill_random_value(vec_a);
	fill_random_value(vec_b);
	//check the result is same
	{
		normal_vector_mul(vec_a, vec_b, vec_result);
		neon_vector_mul(vec_a, vec_b, vec_result2);
		if (!is_equals_vector(vec_result,vec_result2))
		{
			std::cerr << "result vector is not equals!" << std::endl;
			return -1;
		}
	}
	//test normal_vector_mul
	{
		FuncCostTimeHelper time_helper("normal_vector_mul");
		for (int i = 0; i < test_round;i++)
		{
			normal_vector_mul(vec_a, vec_b, vec_result);
		}
	}
	//test neon_vector_mul
	{
		FuncCostTimeHelper time_helper("neon_vector_mul");
		for (int i = 0; i < test_round; i++)
		{
			neon_vector_mul(vec_a, vec_b, vec_result2);
		}
	}
	return 0;
}

int main(int, char*[])
{
	return test_neon();
}

說明:

  • 這段代碼在關閉編譯器的自動向量化優化之后,neon_vector_mul大約比normal_vector_mul速度快3倍左右。
  • 這段代碼中使用了3條NEON指令:vld1q_f32,vmulq_f32,vst1q_f32。具體指令的作用會在后續博文中說明。
  • 此處僅作演示。

參考

  1. DEN0018A_neon_programmers_guide
  2. DDI0487A_f_armv8_arm
  3. DEN0013D_cortex_a_series_PG

歡迎關注公眾號,不定期推送技術以及感想。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM