SIMD指令集介绍

2023-11-21 02:52
文章标签 介绍 指令集 simd

本文主要是介绍SIMD指令集介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

# 介绍

本学期,我们将在多项作业中使用 SIMD(单指令多数据)指令。这些是在称为向量的宽寄存器上运行的指令集。对于我们的作业,这些向量通常为 256 位宽,尽管您可能偶尔使用 128 位版本。通常,作用于这些宽寄存器的指令会将其视为值数组。然后,他们将对数组中的每个值独立执行操作。在硬件中,这可以通过多个并行工作的ALU来实现。因此,尽管这些指令执行的算术比“正常”指令多很多倍,但它们可以与正常指令一样快。

通常,我们将使用“内部函数”访问这些指令。这些函数通常直接对应于特定的汇编指令。这将使我们能够编写始终如一地访问此特殊功能的 C 代码,而不会失去拥有 C 编译器的所有好处。

#  内联参考

我们将使用的内在功能是英特尔定义的接口。因此,英特尔的文档(可在此处找到)是这些功能的综合参考。请注意,本文档包含与实验室计算机上不支持的指令相对应的函数。为避免看到这些,请务必仅选中侧面标有“AVX”、“AVX2”和“SSE”到“SSE4.2”的框。

英特尔的参考资料通常描述了伪代码中的指令,这些指令使用诸如

```
a[63:0] := b[127:64]
```

表示将向量 B 的位 64 到 127(含)分配给向量 A 的位 0 到 63。

#  头文件

若要使用内部函数,需要包含相应的头文件。对于内联函数,我们将使用它是:

```
#include <smmintrin.h>
#include <immintrin.h>
```

# 用 C 语言表示向量

为了表示可能存储在 C 寄存器之一中的 256 位值,我们将使用以下类型之一:

* __m256 (8个float)
* __m256d (4个double)
* __m256i (n个int)

由于其中每个都只是一个 256 位值,因此,如果要使用的函数需要“错误”类型的值,则可以在这些类型之间进行转换。例如,您可能希望使用旨在加载浮点值以加载整数的函数。在内部,期望这些类型的函数只是操作寄存器或内存中的 256 位值。

## 类型和内部函数的 128 位版本

还有 128 位矢量类型和相应的指令。要使用它,在大多数情况下,您可以在类型名称中替换为 _mm256_ 和 _mm_ __m128 在类型名称中替换为 __m256 。

在某些情况下,仅存在 256 位版本的指令。

## 设置和提取值

如果要加载 128 位值的常量,则需要使用内部函数之一。最容易的是,您可以使用名称以 开头 _mm_setr 的函数之一。例如:

```
__m256i values = _mm256_setr_epi32(0x1234, 0x2345, 0x3456, 0x4567, 0x5678, 0x6789, 0x789A, 0x89AB);
```

make 包含 values 8 个 32 位整数, , , 0x3456 , 0x4567 0x1234 0x2345 0x5678 , , , , 0x6789 0x789A . 0x89AB 然后,我们可以通过执行以下操作来提取这些整数中的每一个:

```
int first_value = _mm256_extract_epi32(values, 0);
// first_value == 0x1234
int second_value = _mm256_extract_epi32(values, 1);
// second_value == 0x2345
```

请注意,只能将常量索引传递给 和类似函数的 _mm256_extract_epi32 第二个参数。

## 加载和存储值

要从内存加载值数组或将值数组存储到内存中,我们可以使用以 或 _mm256_storeu 开头 _mm256_loadu 的内联函数:

```
int arrayA[8];
_mm256_storeu_si256((__m128i*) arrayA, values);
// arrayA[0] == 0x1234
// arrayA[1] == 0x2345
// ...

int arrayB[8] = {10, 20, 30, 40, 50, 60, 70, 80};
values = _mm256_loadu_si256((__m128i*) arrayB);
// 10 == arrayB[0] == _mm256_extract_epi32(values, 0)
// 20 == arrayB[1] == _mm256_extract_epi32(values, 1)
// ...
```

##  算术

要实际对值执行算术运算,每个支持的数学运算都有函数。例如:

```
__m256i first_values =  _mm256_setr_epi32(10, 20, 30, 40);
__m256i second_values = _mm256_setr_epi32( 5,  6,  7,  8);
__m256i result_values = _mm256_add_epi32(first_values, second_values);
// _mm_extract_epi32(result_values, 0) == 15
// _mm_extract_epi32(result_values, 1) == 26
// _mm_extract_epi32(result_values, 2) == 37
// _mm_extract_epi32(result_values, 3) == 48
```

## 向量中不同类型的值

这些示例将 256 位值视为 8 个 32 位整数的数组。有一些指令处理许多不同类型的值,包括其他大小的整数或浮点数。您通常可以通过函数名称中指示值类型的存在来判断需要哪种类型。例如,“epi32”表示 an __m256 中的“8 个 32 位值”或 ( __m128 名称代表“扩展打包整数,32 位”)。您将在名称中看到其他一些约定:

* si256 – 有符号 256 位整数
* si128 – 有符号 128 位整数
* epi8 , , epi32 — epi64 有符号 8 位整数(A 中的 32 个和 A __m256 __m128 中的 16 个)或有符号 32 位整数或有符号 64 位整数的向量
* epu8 — 无符号 8 位整数的 vecotr(当操作对有符号和无符号数字的操作之间存在差异时,例如转换为更大的整数或乘法)
* epu16 , epu32 — 无符号 16 位整数或 8 个无符号 32 位整数数组(当操作与有符号不同时)
* ps — “打包单” — 8 个单精度浮子
* pd — “打包双倍” — 4 双倍
* ss — 一个浮点数(仅使用 256 位或 128 位值的 32 位)
* sd — 一个双精度值(仅使用 256 位或 256 位值的 64 位)

#  示例(在 C 中)

以下两个 C 函数是等效的

```
int add_no_AVX(int size, int *first_array, int *second_array) {
    for (int i = 0; i < size; ++i) {
        first_array[i] += second_array[i];
    }
}

int add_AVX(int size, int *first_array, int *second_array) {
    int i = 0;
    for (; i + 8 <= size; i += 8) {
        // load 256-bit chunks of each array
        __m256i first_values = _mm_loadu_si256((__m256i*) &first_array[i]);
        __m256i second_values = _mm_loadu_si256((__m256i*) &second_array[i]);

        // add each pair of 32-bit integers in the 256-bit chunks
        first_values = _mm256_add_epi32(first_values, second_values);
    
        // store 256-bit chunk to first array
        _mm_storeu_si256((__m256i*) &first_array[i], first_values);
    }
    // handle left-over
    for (; i < size; ++i) {
        first_array[i] += second_array[i];
    }
}
```

# 精选的方便的内在函数:

##  算术

* _mm256_add_epi32(a, b) — 将其 __m256i 参数视为 8 个 32 位整数。如果 a 包含 32 位整数 a0, a1, a2, a3, a4, a5, a6, a7 并 b 包含 b0, b1, b2, b3, b4, b5, b6, b7 ,则返回 a0 + b0, a1 + b1, a2 + b2, a3 + b3, a4 + b4, a5 + b5, a6 + a6, a7 + b7 。(与 vpaddd 指令相对应。
* _mm256_add_epi16(a, b) — 与 _mm256_add_epi32 16 位整数相同,但使用 16 位整数。如果 a 包含 16 位整数 a0, a1, ..., a15 并 b 包含 b1, b2, ..., b15 ,则返回 a0 + b0, a1 + b1, ..., a15 + b15 。(与 vpaddw 指令相对应。
* _mm256_add_epi8(a, b) — 与 _mm256_add_epi32 8 位整数相同,但使用 8 位整数。
* _mm256_mullo_epi16(x, y) :将 x 和 y 视为 16 位有符号整数的向量,将每对整数相乘,并将结果截断为 16 位。
* _mm256_mulhi_epi16(x, y) :将 x 和 y 视为 16 位有符号整数的向量,将每对整数相乘得到一个 32 位整数,然后返回每个 32 位整数结果的前 16 位。
* _mm256_srli_epi16(x, N) :处理 x 和 16 位有符号整数的向量,并返回逻辑上将每个右移的结果 N 。(还有 epi32 32 位或 64 位整数的 and epi64 变体。
* _mm256_slli_epi16(x, N) :处理 x 和 16 位有符号整数的向量,并返回将每个向左移动的结果 N 。(还有 epi32 32 位或 64 位整数的 and epi64 变体。
* _mm256_hadd_epi16(a, b) — (“horizontal add”) 将其 __m128i 参数视为 16 位整数的向量。如果 a contains 和 b contains b0, b1, b2, b3, ..., b15 a0, a1, a2, a3, ..., a15 ,则返回 a0 + a1, a2 + a3, a4 + a5, a6 + a7, b0 + b1, b2 + b3, b4 + b5, b6 + b7, a8 + a9, a10 + a11, a12 + a13, a14 + a15, b8 + b9, b10 + b11, b12 + b13, b14 + b15 。请注意,这通常比 _mm_add_epi16 慢得多。(与 vphaddw 指令相对应。

##  加载/存储

* _mm256_loadu_si256 , _mm256_storeu_si256 — 向内存加载或存储 256 位或从内存加载或存储 256 位。请注意,您可以使用 _mm256_storeu_si256 存储到临时数组中,如下所示:

  ```
   unsigned short values_as_array[16];
   __m256i values_as_vector;

   _mm256_storeu_si128((__m256i*) &values_as_array[0], values_as_vector);
  ```
* _mm_loadu_si128 , _mm_storeu_si128 — 将 128 位加载或存储到内存或从内存加载或存储。(对应于 vmovdqu 说明。它们的工作方式与 完全相同, _mm256_loadu_si256 只是它们使用 type __m128i 而不是 __m256i .
* 要存储向量中的 64 位或 32 位,一种方法是使用提取操作和 memcpy:

  ```
   unsigned short first_four_values_as_array[4];
   __m256i values_as_vector;

   *(long*)(&first_four_values_as_array[0]) = _mm256_extract_epi64(values_as_vetor, 0);
  ```

  (此代码实际上不是标准投诉;它违反了“严格别名”规则。但是在 SIMD 分配的 Makefile 中,我们使用 compiler 选项 -fno-strict-aliasing 禁用了它。不违反严格别名规则的替代方法是使用联合,而不是将指针转换为 int* or to use memcpy ,这通常针对小副本进行了优化。
* _mm_cvtsi32_si128 :将 32 位加载到 128 位向量中:

  ```
   unsigned short values[2];
   __m128i values_as_vector; // only using first 32 bits = 2 shorts
   values_as_vector = _mm_cvtsi32_si128( *(int*) &values[0]);
  ```

  (此代码实际上不是标准投诉;它违反了“严格别名”规则。但是在 SIMD 分配的 Makefile 中,我们使用 compiler 选项 -fno-strict-aliasing 禁用了它。不违反严格别名规则的替代方法是使用联合,而不是将指针转换为 int* .)
* _mm_cvtsi32_si128 :将 64 位加载到 128 位向量中:

  ```
   unsigned short values[4];
   __m128i values_as_vector; // only using first 64 bits = 4 shorts
   values_as_vector = _mm_cvtsi64_si128( *(long*) &values[0]);
  ```

  (此代码实际上不是标准投诉;请参阅上面的评论) _mm_cvtsi32_si128
* 要在 256 位向量中加载 32 位或 64 位,可以使用 _mm_cvtsi32_si128 或 _mm_cvtsi32_si256 一起使用 _mm266_zextsi128_si256 将 128 位向量转换为 256 位向量。
* _mm256_maskstore_epi32(int *addr, __m256i mask, __m256i a) — 存储 a at addr 的 32 位值,但仅存储 mask 指定的 32 位值。如果设置了每个 32 位整数 mask 的最高有效位(即符号),则存储值。例如:

  ```
   int values[8] = { 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF };
   __m256i a =    __m256_setr_epi32(1,2,3,4,5,6,7,8);
   __m256i mask = __m256_setr_epi32(0,-1,0,0,-1,0,-1,-1);
   _mm256_maskstore_epi32(&values[0], mask, a);
  ```

  应导致包含以下内容的值

  ```
   { 0xF, 2, 0xF, 0xF, 5, 0xF, 7, 8 }
  ```
* 有关详细信息,请参阅英特尔的参考资料,在“加载”和“存储”类别下

##  设置常量

* _mm256_setr_epi32 — 返回一个 __m256i 包含指定 32 位整数的值。第一个整数参数将位于写入内存时地址最低的部分 __m256i 。例如:

  ```
   __m256i value1 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7);
  ```

  产生 value1 与 in value2 相同的结果

  ```
   int array[8] = {0, 1, 2, 3, 4, 5, 6, 7};
   __m256i value2 = _mm256_loadu_si256((__m256i*) &array[0]);
  ```
* _mm_setr_epi32 — 返回一个 __m128i 包含指定 32 位整数的值。第一个整数参数将位于写入内存时地址最低的部分 __m128i 。例如:

  ```
   __m128i value1 = _mm_setr_epi32(0, 1, 2, 3);
  ```

  产生 value1 与 in value2 相同的结果

  ```
   int array[4] = {0, 1, 2, 3, 4, 5, 6, 7};
   __m128i value2 = _mm_loadu_si128((__m256i*) &array[0]);
  ```
* _mm256_setr_epi16 — 与 _mm256_setr_epi32 16 位整数相同,但使用 16 位整数。例如:

  ```
   __m256i value1 = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
  ```

  产生 value1 与 in value2 相同的结果

  ```
   short array[8] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
   __m256i value2 = _mm256_loadu_si256((__m256i*) &array[0]);
  ```
* _mm256_setr_epi8 , — 与 _mm256_setr_epi32 和 _mm_setr_epi32 相同, _mm_setr_epi8 但具有 8 位整数。
* _mm_set1_epi32 , , — 返回一个 __m128i 值,表示适当大小的值数组, _mm_set1_epi16 _mm_set1_epi8 其中数组的每个元素都具有相同的值。例如:

  ```
   __m128i value = _mm_set1_epi16(42);
  ```

  具有与以下相同的效果:

  ```
   __m128i value = _mm_setr_epi16(42, 42, 42, 42,  42, 42, 42, 42);
  ```
* _mm256_set_epi8 , etc. — 与 _mm256_setr_epi8 等相同,但其参数的顺序相反
* 有关更多信息,请参阅英特尔的参考资料,在“设置”类别下

## 提取部分值

* _mm256_extract_epi32(a, index) 从 256 位向量中提取 index 'th 32 位整数 a 。索引为 0 的整数是将存储在最低内存地址的整数,如果 a 复制到内存中。 index 必须是一个常量。
   例如

  ```
   __m256i a = _mm256_setr_epi32(0, 10, 20, 30, 40, 50, 60, 70);
   int x = _mm256_extract_epi32(a, 2);
  ```

  20 分配给 x 。
* _mm_extract_epi32(a, index) 从 128 位向量中提取 index 'th 32 位整数 a 。 index 必须是常量。
* _mm256_extract_epi16(a, index) 与 _mm256_extract_epi32 16 位整数相同,但具有 16 位整数
* _mm256_extracti128_si256(a, index) 从 256 位向量中提取 index 128 位向量 a 。 index 必须是常量。
   例如

  ```
   __m256i a = _mm256_setr_epi32(0, 10, 20, 30, 40, 50, 60, 70);
   __m128i result = _mm256_extracti128_si256(a, 1);
  ```

   相当于

  ```
   __m128i result = _mm_setr_epi32(40, 50, 60, 70);
  ```
* 有关更多信息,请参阅英特尔的参考资料,搜索“提取”或在“Swizzle”和“Cast”类别下查找。

## 在值类型之间转换

* _mm256_cvtepu8_epi16(eight_bit_numbers) :采用包含 16 个 8 位数字的 128 位向量,并将其转换为包含 16 个 16 位有符号整数的 256 位向量。例如:

  ```
   __m128i value1 = _mm_setr_epi8(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150);
   __m256i value2 = _mm256_cvtepu8_epi16(value1);
  ```

  导致 value2 包含与我们执行的相同的值:

  ```
   __m256i value2 = _mm256_setr_epi16(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150);
  ```
* []()_mm256_packus_epi16(a, b) 获取 256 位向量中的 16 位有符号整数 a , b 并将它们转换为 8 位无符号整数的 256 位向量。结果包含 的前 a 8 个整数,后跟 的前 8 个整数,后跟 的最后 8 个整数 b a ,后跟 的最后 8 个整数 b 。超出范围的值设置为 255 或 0。
   例如:

  ```
   __m256i a = _mm256_setr_epi16(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160);
   __m256i b = _mm256_setr_epi16(170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 25, 15, 5, -5, -15);

   __m256i result = _mm256_packus_epi16(a, b)
  ```

  设置 result 与我们所做的相同:

  ```
   __m256i result = _mm256_setr_epu8(
       10, 20, 30, 40, 50, 60, 70, 80, /* first 8 integers from a */
       170, 180, 190, 200, 210, 220, 230, 240, /* first eight integers from b */
       90, 100, 110, 120, 130, 140, 150, /* last 8 integers from a */
       250, 255, 255, 25, 15, 5, 0, 0,  /* last 8 integers from b */
           /* 260, 270 became 255;  -5, -15 became 0 */
   );
  ```
* _mm256_zextsi128_si256(a) 采用 128 位向量 a ,并通过添加 0 将其转换为 256 位向量。
* 有关更多信息,请参阅英特尔在“Swizzle”和“Move”和“Cast”类别下的参考。

## 重新排列 256 位值

* _mm256_permute2x128_si256(a, b, mask) 采用两个 256 位向量, a 并 b 根据 mask 将这些向量的 128 位半部分组合成一个新的 256 位向量。 mask 是一个单字节整数常量。最低有效半字节指定放置在结果向量的最低地址中的值,最高有效半字节指定放置在结果向量的最高地址中的值。
  每个掩码半字节选择的值为:

  * 0 选择前 128 位 a
  * 1 选择第二个 128 位 a
  * 2 选择前 128 位 b
  * 3 选择第二个 128 位 b
  * 4 到 15 选择常量 0 (忽略 a 和 b 的值)

  例如,要重复 a 的第二个 128 位,可以提供如下示例所示 0x11 的掩码:

  ```
   __m256i a = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
   __m256i b = _mm256_setr_epi32(8, 9, 10, 11, 12, 13, 14, 15);
   __m256i result = _mm256_permute2x128_si256(a, b, 0x11);
   // result == _mm256_setr_epi32(4, 5, 6, 7, 4, 5, 6, 7)
  ```

  要生成前 128 位后跟后跟 1 位后跟 128 位 a b 的结果,将提供如下 0x30 掩码:

  ```
   __m256i a = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
   __m256i b = _mm256_setr_epi32(8, 9, 10, 11, 12, 13, 14, 15);
   __m256i result = _mm256_permute2x128_si256(a, b, 0x30);
   // result == _mm256_setr_epi32(0, 1, 2, 3, 12, 13, 14, 15)
  ```
* _mm256_unpackhi_epi16(a, b) 将 16 位整数与 256 位向量中每个 128 位半部分的上四分之一交错, a 然后 b .例如:

  ```
   __m256i a = _mm256_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
   __m256i b = _mm256_setr_epi16(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
   __m256i result = _mm256_unpackhi_epi16(a, b);
  ```

  与

  ```
   __m256i result = _mm256_setr_epi16(
       /* top quarter of first half of a and b */
       4, 20, 5, 21, 6, 22, 7, 23,
       /* top quarter of second half of a and b */
       12, 28, 13, 29, 14, 30, 15, 31
   )
  ```
* _mm256_unpacklo_epi16(a, b) 就像, _mm256_unpackhi_epi16 但它从 和 的每半部分 a b 的底部四分之一取 16 位整数
* _mm256_permutevar8x32_epi32(x, indexes) — 通过为向量中的每个 32 位索引生成一个 32 位值的向量 indexes ,从向量中检索该索引处的 32 位值 x 并将其放入结果中。例如:

  ```
   __m256i x = _mm256_setr_epi32(10, 20, 30, 40, 50, 60, 70, 80)
   __m256i indexes = _mm256_setr_epi32(3, 3, 0, 1, 2, 3, 6, 7);
   __m256i result = _mm256_permutevar8x32_epi32(x, indexes)
  ```

  等同于:

  ```
   __m256i reuslt = _mm256_setr_epi32(40, 40, 10, 20, 30, 70, 80);
  ```
* 有关更多信息,请参阅英特尔在“Swizzle”和“Move”以及“Cast”和“Shift”类别下的参考。

## 重新排列 128 位值

* _mm_unpackhi_epi16(a, b) 将 128 位向量上半部分的 16 位整数交错, a 然后 b .例如:

  ```
   __m128i a = _mm_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7);
   __m128i b = _mm_setr_epi16(8, 9, 10, 11, 12, 13, 14, 15);
   __m256i result = _mm_unpackhi_epi16(a, b);
  ```

  与

  ```
   __m128i result = _mm_setr_epi16(
       4, 20, 5, 21, 6, 22, 7, 23,
   )
  ```
* _mm_shuffle_epi8(a, mask) 重新排列 a 根据 的 mask 字节并返回结果。 mask 是 8 位整数 (type __m128i ) 的向量,指示如何重新排列每个字节:

  * 如果掩码中的字节设置了高位(大于 127),则输出的相应字节为 0;
  * 否则,输入中指定的字节号将复制到输出的相应字节。字节使用 0 进行编号,以表示如果将向量复制到内存中,将存储在最低地址中的字节。

   例如:

  ```
   __m128i value1 = _mm_setr_epi8(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160);
   __m128i mask = _mm_setr_epi8(0x80, 0x80, 0x80, 5, 4, 3, 0x80, 7, 6, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
   __m128i value2 = _mm_shuffle_epi8(value1, mask);
  ```

  应产生与以下结果相同的结果:

  ```
   __m128i value2 = _mm_setr_epi8(0, 0, 0, 60, 50, 40, 0, 80, 70, 0, 0, 0, 0, 0, 0, 0, 0);
       /* e.g. since 3rd element of mask is 5, 3rd element of output is 60, element 5 of the input */
  ```
* 有关更多信息,请参阅英特尔在“Swizzle”和“Move”以及“Cast”和“Shift”类别下的参考。

# 示例(组装指令)

 指令

```
      paddd %xmm0, %xmm1

```

接收两个 128 位值,一个在寄存器中,另一个在寄存器 %xmm0 %xmm1 中。这些寄存器中的每一个都被视为两个 64 位值的数组。将每对 64 位值相加,并将结果存储在 %xmm1 中。

例如,如果 %xmm0 包含 128 位值(以十六进制写入):

```
0x0000 0000 0000 0001 FFFF FFFF FFFF FFFF 
```

并 %xmm1 包含 128 位值(以十六进制写入):

```
0xFFFF FFFF FFFF FFFE 0000 0000 0000 0003 
```

然后 %xmm0 ,将被视为包含数字和 (或 0xFFFFFFFFFFFFFFFF ),并 %xmm1 被视为包含数字 -2 1 和 -1 3 。 paddd 将添加 1 和 -2 to produce -1 and -1 and 3 to produce 2, so the final value of %xmm1' 将是:

```
0xFFFF FFFF FFFF FFFF 0000 0000 0000 0002
```

如果我们将此值解释为两个 64 位整数的数组,则为 -1 和 2 。

这篇关于SIMD指令集介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/399323

相关文章

揭秘未来艺术:AI绘画工具全面介绍

📑前言 随着科技的飞速发展,人工智能(AI)已经逐渐渗透到我们生活的方方面面。在艺术创作领域,AI技术同样展现出了其独特的魅力。今天,我们就来一起探索这个神秘而引人入胜的领域,深入了解AI绘画工具的奥秘及其为艺术创作带来的革命性变革。 一、AI绘画工具的崛起 1.1 颠覆传统绘画模式 在过去,绘画是艺术家们通过手中的画笔,蘸取颜料,在画布上自由挥洒的创造性过程。然而,随着AI绘画工

20.Spring5注解介绍

1.配置组件 Configure Components 注解名称说明@Configuration把一个类作为一个loC容 器 ,它的某个方法头上如果注册7@Bean , 就会作为这个Spring容器中的Bean@ComponentScan在配置类上添加@ComponentScan注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan>@Sc

C++标准模板库STL介绍

STL的六大组成部分 STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,提供了丰富的通用数据结构和算法,使得 C++ 编程变得更加高效和方便。STL 包括了 6 大类组件,分别是算法(Algorithm)、容器(Container)、空间分配器(Allocator)、迭代器(Iterator)、函数对象(Functor)、适配器(Adapter)

一二三应用开发平台应用开发示例(4)——视图类型介绍以及新增、修改、查看视图配置

调整上级属性类型 前面为了快速展示平台的低代码配置功能,将实体文件夹的数据模型上级属性的数据类型暂时配置为文本类型,现在我们调整下,将其数据类型调整为实体,如下图所示: 数据类型需要选择实体,并在实体选择框中选择自身“文件夹” 这时候,再点击生成代码,平台会报错,提示“实体【文件夹】未设置主参照视图”。这是因为文件夹选择的功能页面,同样是基于配置产生的,因为视图我们还没有配置,所以会报错。

49-1 内网渗透 - Bypass UAC介绍

一、Bypass UAC         用户账户控制(UAC)是Windows操作系统的一种安全机制,旨在防止未经授权的应用程序自动安装并防止非授权修改系统设置。它确保应用程序和任务通常在非管理员账户的安全上下文中运行,除非明确需要管理员权限,用户才会被提示确认。         对于非RID 500的管理员用户(除了内置的Administrator账户),当他们登录时,系统会为其分配两个访

okhttp3的详细介绍

这篇文章说下OkHttp的基本用法,是最新的3哦,如果你曾经在网上搜索OkHttp怎么使用发现有些类没有了可能是因为人家说的是2。首先说下OkHttp3是Java和Android都能用,Android还有一个著名网络库叫Volley,那个只有Android能用。导入自己到入jar包,别漏了okio:okhttp-3.3.0.jarokio-1.8.0.jarmaven方式:<dependen

Retrofit详细介绍

转载请标明出处:http://blog.csdn.net/xx326664162/article/details/51910837 文章出自:薛瑄的博客 你也可以查看我的其他同类文章,也会让你有一定的收货! 一、Retrofit 是什么? Retrofit is a type-safe HTTP client for Android and java. Retrofit 是与 Web 服务器提

Retrofit介绍案例

Retrofit这东西我就不多做解释了,反正最近应用很广,基本都快和OkHttp一起成为安卓的事实网络访问标准框架了。   这么好的一个东西,官网文档实在是不算太好,说的不太清晰。按官网的经常会有“Could not locate ResponseBody converter for”问题。 反正折腾了一番,终于跑出来了一个例子。这里把正确的例子写出来,方便大家参考。 首先要注意

okclient2详细介绍

在 Java 程序中经常需要用到 HTTP 客户端来发送 HTTP 请求并对所得到的响应进行处理。比如屏幕抓取(screen scraping)程序通过 HTTP 客户端来访问网站并解析所得到的 HTTP 文档。在 Java 服务端程序中也可能需要使用 HTTP 客户端来与第三方 REST 服务进行集成。随着微服务(microservices)的流行,HTTP 成为不同服务之间的标准集成方式。H

Hbase特性介绍

1、什么是Hbase。 是一个高可靠性、高性能、列存储、可伸缩、实时读写的分布式数据库系统。 适合于存储非结构化数据,基于列的而不是基于行的模式 如图:Hadoop生态中HBase与其他部分的关系。 2、关系数据库已经流行很多年,并且Hadoop已经有了HDFS和MapReduce,为什么需要HBase? Hadoop可以很好地解决大规模数据的离线批量处理问题,但是,受限于Hadoo