SIMD加速矩阵运算

news2024/9/28 11:02:01

一、SIMD指令简介

  • SIMD的全称叫做,单指令集多数据(Single Instruction Multiple Data)。最直观的理解就是,向量计算。比如一个加法指令周期只能算一组数(一维向量相加),使用SIMD的话,一个加法指令周期可以同时算多组数(n维向量相加),二者用时基本相等,极大地提高了运算效率。
  • SIMD (Single Instruction Multiple Data)指令集,指单指令多数据流技术,可用一组指令对多组数据通进行并行操作。SIMD指令可以在一个控制器上控制同时多个平行的处理微元,一次指令运算执行多个数据流,这样在很多时候可以提高程序的运算速度。SIMD指令在本质上非常类似一个向量处理器,可对控制器上的一组数据(又称“数据向量”) 同时分别执行相同的操作从而实现空间上的并行。SIMD是CPU实现DLP(Data Level Parallelism)的关键,DLP就是按照SIMD模式完成计算的。SSE和较早的MMX和 AMD的3DNow!都是SIMD指令集。它可以通过单指令多数据技术和单时钟周期并行处理多个浮点来有效地提高浮点运算速度
  • 可以使用CPU-Z程序查看自己设备的CPU支持哪些SIMD运算指令集。
    在这里插入图片描述

二、核心代码

  • 矩阵的声明如下:
pragma once
#include <immintrin.h>//AVX(include wmmintrin.h)
#include <initializer_list>
#include "Vector3f.h"
#include "Myth.h"

class Matrix4
{
public:

	Matrix4();
	Matrix4(const std::initializer_list<float>& list);
	~Matrix4();

	Matrix4 operator+(const Matrix4& right)const;
	Matrix4 operator-(const Matrix4& right)const;
	Matrix4 operator*(const Matrix4& right)const;
	Vector3f operator*(const Vector3f& v)const;
	Matrix4 operator*(float k)const;
	Matrix4 operator-()const;

	void Identity();
	Vector3f MultiplyVector3(const Vector3f& v) const;
	Matrix4 transpose()const;
	void Print();

public:
	union
	{
		__m256 m[2];
		float data[16];
		float ptr[4][4];
	};
};
  • 我们要实现矩阵和矩阵加减运算、矩阵和矩阵乘法运算、矩阵和向量乘法运算、矩阵和常数乘法运算的加速。

2.1 矩阵的构造

  • 使用__m256来存储矩阵数据节省了每次运算时将数据加载到__m256变量所需的时间,但也因此带来获取矩阵元素的不变,对此我们使用union共用体来解决这个问题。
  • 使用union共用体定义的变量共用同一块内存区域,由于m、data和ptr所需内存字节数相同,因此无论使用哪一种方式索引数据都会得到正确的结果。
  • 注意当使用容器来存储此矩阵时,可能发生出乎意料的结果,比如 vector< Matrix4 > 会在push_back时改变容器中所有元素的值为新添加元素的值,原因未知。

2.2 矩阵和矩阵加减法运算

  • 重载矩阵和矩阵之间的加法运算符,我们使用_mm256_add_ps函数一次计算8个float变量的求和,因此两次调用函数花费两次时钟周期即可完成矩阵加法的运算。
Matrix4 Matrix4::operator + (const Matrix4& right) const
{
	Matrix4 res;
	
	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_add_ps(m[i], right.m[i]);
	return res;
}
  • 对于矩阵间减法运算,和加法模板相同,将加法函数 _mm256_add_ps 改为减法函数 _mm256_sub_ps即可。
Matrix4 Matrix4::operator - (const Matrix4& right) const
{
	Matrix4 res;

	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_sub_ps(m[i], right.m[i]);
	return res;
}

2.3 矩阵和常数相乘

  • 对于矩阵和常数的乘法,我们首先使用_mm256_set_ps函数构造一个分量都为k的8维向量,使用乘法函数 _mm256_mul_ps令矩阵和8维向量对应相乘即可。
  • 当我们使用SIMD时会涉及很多运算以意外的操作,比如常见的我们需要构造SIMD类型的变量,这时加载数据就需要用额外的时间,因此SIMD不能达到理论上超过普通运算几倍的速度。
Matrix4 Matrix4::operator*(float k)const
{
	Matrix4 res;
	__m256 mt = _mm256_set_ps(k, k, k, k, k, k, k, k);
	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_mul_ps(m[i], mt);
	return res;
}

2.4 矩阵和向量相乘

  • 对于矩阵和4维向量相乘,我们首先构造一个8维向量来存储向量4维向量,这里的四维向量是一个齐次坐标即其本质是空间中的一个三维向量。我们将8维向量的分量依次设置为xyzwxyzw,这里的xyzw指4维向量的分量,即将4维向量按顺序重复的平铺到8维向量中。
  • 我们使用_mm256dp_ps函数进行乘法并求和运算。_mm256dp_ps函数的前两个参数为8维向量__m256,第三个参数指定运算的规则。此函数将每个__m256变量分为前后两个部分,每个部分占4个float字节,函数依据设置的运算规对两个__m256变量的前后两部分分别实现相乘并求和。0b11110001中前四位1111表示要将两个__m256的前部分各个float对应相乘求和,后部分同样如此。而后四位0001表示将前4个float相乘求和的结果存储到返回值temp[0],将后4个float相乘求和的结果存储到返回值temp[4]中。
  • 现在你也许理解了我们为什么要将4维向量平铺到__m256中了,这样一次我们可以计算出矩阵的两行和向量列的乘法并求和,因此我们调用两次_mm256_dp_ps即可完成所有运算。
Vector3f Matrix4::operator*(const Vector3f& v)const
{
	Vector3f res;
	__declspec(align(16))	__m256 temp;
	__declspec(align(16))	__m256 mt = _mm256_set_ps(v.x, v.y, v.z, v.w, v.x, v.y, v.z, v.w);

	temp = _mm256_dp_ps(m[0], mt, 0b11110001);
	res.x = temp.m256_f32[0];
	res.y = temp.m256_f32[4];

	temp = _mm256_dp_ps(m[1], mt, 0b11110001);
	res.z = temp.m256_f32[0];
	res.w = temp.m256_f32[4];
	
	return res;
}

2.5 矩阵和矩阵相乘

  • 矩阵乘法是在计算机图形学中使用最多的,无论任何物体需要渲染,它的每一个顶点都需要进行数次的矩阵相乘运算,在现代游戏中需要渲染的物体成千上万,顶点更是数不胜数,因此矩阵相乘的速度很大程度上决定了渲染的速度。
  • __declspec(align(16))可以保证字节对齐,建立在定义任何SIMD变量时进行使用。下文中的gather都是取出矩阵的固定位置元素。gatherA12表示取出左矩阵的第一行和第二行,要注意_mm256_set_epi32中索引的顺序是逆序的。
  • 下列代码中gatherA12取出矩阵M的第7, 6, 5, 4, 3, 2, 1, 0个元素,因为是逆序因此其返回的是M(0),M(1)…,M(7)。我们为定义矩阵的union共用体中包含一个一维数组data,直接使用其作为参数即可。因此 __m256 a12 = _mm256_i32gather_ps(this->data, gatherA12, sizeof(float)); 表示从一维数组data中按照gatherA12索引取出元素,每个元素的大小为sizeof(float),这样可以构造的构造矩阵行列对应的SIMD变量。矩阵乘法就是行列相乘求和,因此如矩阵和向量相乘一样,调用 _mm256_dp_ps 将构造的行列相乘求和即可。
__declspec(align(16)) __m256i gatherA12 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);
__declspec(align(16)) __m256i gatherA34 = _mm256_set_epi32(15, 14, 13, 12, 11, 10, 9, 8);

__declspec(align(16)) __m256i gatherB11 = _mm256_set_epi32(12, 8, 4, 0, 12, 8, 4, 0);
__declspec(align(16)) __m256i gatherB22 = _mm256_set_epi32(13, 9, 5, 1, 13, 9, 5, 1);
__declspec(align(16)) __m256i gatherB33 = _mm256_set_epi32(14, 10, 6, 2, 14, 10, 6, 2);
__declspec(align(16)) __m256i gatherB44 = _mm256_set_epi32(15, 11, 7, 3, 15, 11, 7, 3);

Matrix4 Matrix4::operator*(const Matrix4& right)const
{
	Matrix4 ret;
	__declspec(align(16)) __m256 temp;
	__declspec(align(16)) __m256 a12, a34;
	__declspec(align(16)) __m256 b11, b22, b33, b44;

	a12 = _mm256_i32gather_ps(this->data, gatherA12, sizeof(float));
	a34 = _mm256_i32gather_ps(this->data, gatherA34, sizeof(float));

	b11 = _mm256_i32gather_ps(right.data, gatherB11, sizeof(float));
	b22 = _mm256_i32gather_ps(right.data, gatherB22, sizeof(float));
	b33 = _mm256_i32gather_ps(right.data, gatherB33, sizeof(float));
	b44 = _mm256_i32gather_ps(right.data, gatherB44, sizeof(float));

	temp = _mm256_dp_ps(a12, b11, 0b11110001);
	ret.data[0] = temp.m256_f32[0];
	ret.data[4] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b11, 0b11110001);
	ret.data[8] = temp.m256_f32[0];
	ret.data[12] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b22, 0b11110001);
	ret.data[1] = temp.m256_f32[0];
	ret.data[5] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b22, 0b11110001);
	ret.data[9] = temp.m256_f32[0];
	ret.data[13] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b33, 0b11110001);
	ret.data[2] = temp.m256_f32[0];
	ret.data[6] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b33, 0b11110001);
	ret.data[10] = temp.m256_f32[0];
	ret.data[14] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b44, 0b11110001);
	ret.data[3] = temp.m256_f32[0];
	ret.data[7] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b44, 0b11110001);
	ret.data[11] = temp.m256_f32[0];
	ret.data[15] = temp.m256_f32[4];

	return ret;
}

三、完整代码

  • __m256可以存储256个字节,即8个float变量,我们使用__m256的二维数组即可表示一个4X4的浮点矩阵。使用到的指令集为AVX指令集,在头文件<immintrin.h>中。
  • 如何 #define SIMD 1 则启动SIMD矩阵加速运算,否则使用普通运算。
  • 矩阵的完整定义如下:
#pragma once
#include <immintrin.h>//AVX(include wmmintrin.h)
#include <initializer_list>
#include "Vector3f.h"
#include "Myth.h"

class Matrix4
{
public:

	Matrix4();
	Matrix4(const std::initializer_list<float>& list);
	~Matrix4();

	Matrix4 operator+(const Matrix4& right)const;
	Matrix4 operator-(const Matrix4& right)const;
	Matrix4 operator*(const Matrix4& right)const;
	Vector3f operator*(const Vector3f& v)const;
	Matrix4 operator*(float k)const;
	Matrix4 operator-()const;

	void Identity();
	Vector3f MultiplyVector3(const Vector3f& v) const;
	Matrix4 transpose()const;
	void Print();

public:
	union
	{
		__m256 m[2];
		float data[16];
		float ptr[4][4];
	};
};

  • 使用SIMD加速的矩阵的运算操作定义如下。
  • 当定义宏SIMD的值为1时使用SIMD加速运算,否则使用普通运算。
  • 矩阵的加减法直接使用AVX指令一次运算8个float变量,因此加减法的纯运算时间为两个时钟周期。如果您的硬件支持AVX5,那么可以一次运算16个float变量,即可再次缩短运算时间。
#include "Matrix.h"

Matrix4::Matrix4()
{
	Identity();
}
Matrix4::Matrix4(const std::initializer_list<float>& list)
{
	auto begin = list.begin();
	auto end = list.end();
	int i = 0;
	while (begin != end)
	{
		data[i++] = *begin;
		++begin;
	}
}
Matrix4::~Matrix4()
{
}

#if SIMD

Matrix4 Matrix4::operator + (const Matrix4& right) const
{
	Matrix4 res;
	
	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_add_ps(m[i], right.m[i]);
	return res;
}
Matrix4 Matrix4::operator - (const Matrix4& right) const
{
	Matrix4 res;

	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_sub_ps(m[i], right.m[i]);
	return res;
}
Matrix4 Matrix4::operator*(float k)const
{
	Matrix4 res;
	__m256 mt = _mm256_set_ps(k, k, k, k, k, k, k, k);
	for (int i = 0; i < 2; i++)
		res.m[i] = _mm256_mul_ps(m[i], mt);
	return res;
}
Vector3f Matrix4::operator*(const Vector3f& v)const
{
	Vector3f res;
	__declspec(align(16))	__m256 temp;
	__declspec(align(16))	__m256 mt = _mm256_set_ps(v.x, v.y, v.z, v.w, v.x, v.y, v.z, v.w);

	temp = _mm256_dp_ps(m[0], mt, 0b11110001);
	res.x = temp.m256_f32[0];
	res.y = temp.m256_f32[4];

	temp = _mm256_dp_ps(m[1], mt, 0b11110001);
	res.z = temp.m256_f32[0];
	res.w = temp.m256_f32[4];
	
	return res;
}


__declspec(align(16)) __m256i gatherA12 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);
__declspec(align(16)) __m256i gatherA34 = _mm256_set_epi32(15, 14, 13, 12, 11, 10, 9, 8);

__declspec(align(16)) __m256i gatherB11 = _mm256_set_epi32(12, 8, 4, 0, 12, 8, 4, 0);
__declspec(align(16)) __m256i gatherB22 = _mm256_set_epi32(13, 9, 5, 1, 13, 9, 5, 1);
__declspec(align(16)) __m256i gatherB33 = _mm256_set_epi32(14, 10, 6, 2, 14, 10, 6, 2);
__declspec(align(16)) __m256i gatherB44 = _mm256_set_epi32(15, 11, 7, 3, 15, 11, 7, 3);

Matrix4 Matrix4::operator*(const Matrix4& right)const
{
	Matrix4 ret;
	__declspec(align(16)) __m256 temp;
	__declspec(align(16)) __m256 a12, a34;
	__declspec(align(16)) __m256 b11, b22, b33, b44;

	a12 = _mm256_i32gather_ps(this->data, gatherA12, sizeof(float));
	a34 = _mm256_i32gather_ps(this->data, gatherA34, sizeof(float));

	b11 = _mm256_i32gather_ps(right.data, gatherB11, sizeof(float));
	b22 = _mm256_i32gather_ps(right.data, gatherB22, sizeof(float));
	b33 = _mm256_i32gather_ps(right.data, gatherB33, sizeof(float));
	b44 = _mm256_i32gather_ps(right.data, gatherB44, sizeof(float));

	temp = _mm256_dp_ps(a12, b11, 0b11110001);
	ret.data[0] = temp.m256_f32[0];
	ret.data[4] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b11, 0b11110001);
	ret.data[8] = temp.m256_f32[0];
	ret.data[12] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b22, 0b11110001);
	ret.data[1] = temp.m256_f32[0];
	ret.data[5] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b22, 0b11110001);
	ret.data[9] = temp.m256_f32[0];
	ret.data[13] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b33, 0b11110001);
	ret.data[2] = temp.m256_f32[0];
	ret.data[6] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b33, 0b11110001);
	ret.data[10] = temp.m256_f32[0];
	ret.data[14] = temp.m256_f32[4];

	temp = _mm256_dp_ps(a12, b44, 0b11110001);
	ret.data[3] = temp.m256_f32[0];
	ret.data[7] = temp.m256_f32[4];
	temp = _mm256_dp_ps(a34, b44, 0b11110001);
	ret.data[11] = temp.m256_f32[0];
	ret.data[15] = temp.m256_f32[4];

	return ret;
}

#else

Matrix4 Matrix4::operator + (const Matrix4& right) const
{
	Matrix4 res;
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			res.ptr[i][j] = ptr[i][j] + right.ptr[i][j];
	return res;
}
Matrix4 Matrix4::operator - (const Matrix4& right) const
{
	Matrix4 res;
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			res.ptr[i][j] = ptr[i][j] - right.ptr[i][j];
	return res;
}

Matrix4 Matrix4::operator*(float k)const
{
	Matrix4 res;
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			res.ptr[i][j] = ptr[i][j] * k;
		}
	}
	return res;
}


Vector3 Matrix4::operator*(const Vector3& v)const
{
	float x = v.x * ptr[0][0] + v.y * ptr[0][1] + v.z * ptr[0][2] + v.w * ptr[0][3];
	float y = v.x * ptr[1][0] + v.y * ptr[1][1] + v.z * ptr[1][2] + v.w * ptr[1][3];
	float z = v.x * ptr[2][0] + v.y * ptr[2][1] + v.z * ptr[2][2] + v.w * ptr[2][3];
	float w = v.x * ptr[3][0] + v.y * ptr[3][1] + v.z * ptr[3][2] + v.w * ptr[3][3];
	Vector3 returnValue(x, y, z);
	returnValue.w = w;
	return returnValue;
}

Matrix4 Matrix4::operator * (const Matrix4& right) const
{
	Matrix4 res;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			res.ptr[i][j] = 0;//temp
			for (int k = 0; k < 4; k++)
			{
				res.ptr[i][j] += this->ptr[i][k] * right.ptr[k][j];
			}
		}
	}
	return res;
}

#endif // SIMD


Matrix4 Matrix4::operator-()const
{
	Matrix4 trans;
	for (int i = 0; i < 4; ++i)
		for (int j = 0; j < 4; ++j)
			trans.ptr[i][j] = ptr[j][i];
	return trans;
}
Matrix4 Matrix4::transpose()const
{
	return -(*this);
}


void Matrix4::Identity()
{
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (i != j)
				ptr[i][j] = 0;
			else
				ptr[i][j] = 1;
		}
	}
}

Vector3f Matrix4::MultiplyVector3(const Vector3f& v) const
{
	return (*this) * v;
}


void Matrix4::Print()
{
	std::cout << "-----------------Matrix Begin--------------" << std::endl;
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			std::cout << "[" << ptr[i][j] << "]   ";
		}
		std::cout << std::endl;
	}
	std::cout << "-----------------Matrix End----------------" << std::endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/636867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

应届生求职个人简历模板(合集)

应届生求职个人简历模板1 基本信息 姓 名&#xff1a; 性别&#xff1a; 婚姻状况&#xff1a; 民族&#xff1a; 户 籍&#xff1a; 年龄&#xff1a; 现所在地&#xff1a; 身高&#xff1a; 联系电话&#xff1a; 电子邮箱&#xff1a;__ 求职意向 希望岗位&#xff1a;Web前…

【建议】强烈推荐ES6函数自由传参的写法,针对方法体的可扩展性很有帮助

let fun({a,b,c}{a:1,b:2,c:3})>{console.log(a);console.log(b);console.log(c); } 接下来我们即可自由传参&#xff0c;需要什么就传递什么 以上写法非常适用于需求变更的情况下自由传递参数&#xff0c;而且形参的数量、传参先后顺序可以根据业务自由搭配&#xff0c;非常…

Feign接口windows启动调用正常,Linux环境调用404

1、Linux环境启动之后报错 如下&#xff1a; windows 是调用正常得 反复测试好几轮 还是这样 &#xff0c;nacos都是注册进去得 helper-service 调用 xTIMS-Web 解决&#xff1a;FeignClient注解 不配置URL会出现那样问题&#xff0c; 配置URL之后 解决 &#xff0c;不报错了…

延迟队列--DelayQueue(JDK)

JDK自身支持延迟队列的数据结构&#xff0c;其实类&#xff1a;java.util.concurrent.DelayQueue。 我们通过阅读源码的方式理解该延迟队列类的实现过程。 1.定义 DelayQueue:是一种支持延时获取元素的无界阻塞队列。 特性&#xff1a; 线程安全&#xff1b; 内部元素有“…

人工智能算力需求稳增,中国将持续夯实算力底座

中国始终强调科技兴国的重要性。数字经济时代&#xff0c;技术的力量更为凸显。近年来&#xff0c;中国政府相关部门相继发布一系列政策&#xff0c;更加明确了人工智能对于提升中国核心竞争力的重要支撑作用&#xff0c;加上新基建、数字经济等持续利好政策的推动&#xff0c;…

0202性能分析-索引-MySQL

1 索引语法 创建索引 CREATE [UNIQUE|FULLTEXT] INDEX index_name ON table_name(index_column_name,...);Index_name&#xff1a;规范为idx_表名_字段名... 查看索引 SHOW INDEX FROM table_name;删除索引 DROP INDEX index_name ON table_name;按照下列要求&#xff0c;创建…

仿交易猫链接 跳转APP功能

最新仿交易猫假链接&#xff0c;带有跳转APP功能 下载程序&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

C语言数据结构——循环链表

如果人生会有很长,愿有你的荣耀永不散场。——《全职高手》 一 . 循环单链表 循环单链表是单链表的另一种形式&#xff0c;其结构特点是&#xff0c;链表中最后一个结点的指针域不再是结束标记&#xff0c;而是指向整个链表的第一个结点&#xff0c;从而使链表形成一个环。 和单…

PLC现场安装和维护的注意事项

虽然PLC是专门在现场使用的控制装置&#xff0c;在设计制造时已采取了很多措施&#xff0c;使它对工业环境比较适应&#xff0c;但是为了确保整个系统稳定可靠&#xff0c;还是应当尽量使PLC有良好的工作环境条件&#xff0c; 并采取必要的抗干扰措施。因此&#xff0c;PLC在安…

python中,unicode对象怎么转换成dict?

python中&#xff0c;unicode对象怎么转换成dict&#xff1f; 使用loads两次

「展会前线」易天光通信盛装亮相2023越南通讯展会

2023年6月7日&#xff0c;在历经了忙碌有序的前期准备工作后&#xff0c;易天光通信销售团队带着满满的信心踏上了越南通讯展会之旅&#xff01; “千呼万唤始出来&#xff0c;犹抱琵琶半遮面”。2023年6月8日&#xff0c;各方期待已久的2023越南通讯展会在越南胡志明市正式开…

肠道有害菌属——假单胞菌属(Pseudomonas),多变且适应性强

谷禾健康 假单胞菌属&#xff08;Pseudomonas&#xff09;是最多样化和普遍存在的细菌属之一&#xff0c;其物种存在于沉积物、临床样本、植物&#xff08;或植物根际&#xff09;、患病动物、水、土壤、海洋、沙漠等&#xff0c;这反映在它们多变的代谢能力和广泛的适应环境的…

3款好用的客户系统管理软件推荐,你用过哪款?

进行客户资料管理确实很重要。我本人在工作中也常常遇到客户关系管理的难题&#xff0c;有时候忘记填写客户信息&#xff0c;亦或是填错信息等场景&#xff0c;甚至会造成许多尴尬局面。为了解决这个问题&#xff0c;我也试用了很多个方法来提高效率。下面我想谈一谈我本人在摸…

十肽-4/Decapeptide-10, CG-IDP2——有效逆转皮肤衰老

简介----十肽-4 十肽-4可以穿透真皮增加胶原蛋白&#xff0c;通过从内至外的重建来逆转皮肤老化的过程&#xff1b;刺激胶原蛋白、弹力纤维和透明质酸增生&#xff0c;提高肌肤的含水量和锁水度&#xff0c;增加皮肤厚度以及减少细纹。 功效与应用----十肽-4 抗皱抗衰老 改善…

浪潮 KaiwuDB x 大数据中心 | 数据驱动政府治理能力快速提升

业务背景 我国工业互联网大数据资源存在孤立、分散、封闭等问题&#xff0c;数据价值未能得到有效利用&#xff0c;数据主权和数据安全面临重大威胁。 发挥数据对工业经济的基础支撑和创新引擎作用&#xff0c;可促进工业互联网的创新发展&#xff0c;加速数据驱动政府治理能…

Pycharm中的find usages有什么用?

问题描述&#xff1a;我们经常使用Pycharm作为开发工具&#xff0c;我们右键会发现有个find usages功能。 比如&#xff0c;我们以YOLOv8中的detect/train.py中的DetectionTrainer()类为例&#xff0c;右键之后如下图所示。 答案&#xff1a;全局搜索&#xff0c;查找类、变量…

「最新」Parallels Desktop 18 for Mac(Pd虚拟机) 18.3.1通用版

Parallels Desktop 18是一款虚拟机软件&#xff0c;能够让Mac电脑上运行Windows、Linux和其他操作系统的应用程序。 此版本的Parallels Desktop 18提供了多项功能增强和改进&#xff0c;包括更快的性能、更好的图形处理、更简单的导入和导出虚拟机等。该软件还支持Apple M1芯片…

QT使用按钮打开新窗口

需求说明&#xff1a;主窗口名为mainwindow&#xff0c;在主窗口添加一个按钮&#xff0c;通过点击按钮能打开一个新的窗口。 第一步&#xff1a;在主窗口添加按钮 找到左边菜单栏的按钮控件拖出置窗口上 第二步&#xff1a;在工程里新建窗口 1.右击最顶层项目文件名&#x…

Springcloud之Feign、Hystrix、Ribbon如何设置超时时间

一&#xff0c;概述 我们在微服务调用服务的时候&#xff0c;会使用hystrix、feign和ribbon&#xff0c;比如有一个实例发生了故障而该情况还没有被服务治理机制及时的发现和摘除&#xff0c;这时候客户端访问该节点的时候自然会失败。 所以&#xff0c;为了构建更为健壮的应…

Vue3系列--provide与inject

目录 Provide inject 在Vue3项目开发的过程中&#xff0c;会创建很多组件&#xff0c;那么避免不了组件之间的通信&#xff0c;在父子组件通信我们可以使用defineProps、defineEmits、defineExpose和Emit方法完成通信&#xff0c;在使用这些方法的前提是需要引用对应的组件。…