4. 初探MPI——集体通信

系列文章目录

  1. 初探MPI——MPI简介
  2. 初探MPI——(阻塞)点对点通信
  3. 初探MPI——(非阻塞)点对点通信
  4. 初探MPI——集体通信

文章目录

  • 系列文章目录
  • 前言
  • 一、集体通信以及同步点
  • 二、`MPI_Bcast` 广播
    • 2.1 使用`MPI_Send` 和 `MPI_Recv` 来做广播
    • 2.2 `MPI_Bcast` 和 `MPI_Send` 以及 `MPI_Recv` 的比较
    • 2.3 Blocking or non-blocking ?
  • 三、`MPI Scatter`, `Gather`, and `Allgather`
    • 3.1 `MPI_Scatter`介绍
    • 3.2 `MPI_Gather` 的介绍
    • 3.3 使用 `MPI_Scatter` 和 `MPI_Gather` 来计算平均数
    • 3.4 `MPI_Allgather`
  • 四、并行排名
    • 4.1 问题概述
    • 4.2 并行排名API定义
    • 4.3 解决并行排名问题
      • 4.3.1 对所有进程中的数字进行排序
      • 4.3.2 排序数字并维护所属
      • 4.3.3 整合
    • 4.4 最终结果
  • 五、`MPI Reduce` and `Allreduce`
    • 5.1 归约/归化(reduce)简介
    • 5.2 `MPI_Reduce`
    • 5.3 使用`MPI_Reduce`计算均值
    • 5.4 `MPI_Allreduce`
      • 5.4.1 使用 `MPI_Allreduce` 计算标准差
  • 总结
  • 参考


前言

点对点通信的方式只会涉及两个不同进程之间的通信。而集体通信指的是涉及 communicator 里面所有进程的一个方法。

接下来的内容将要讲述:

  • Broadcast : One process sends a message to every other process
  • Reduction : One process gets data from all the other processes and applies an operation on it (sum, minimum, maximum, etc.
  • Scatter : A single process partitions the data to send pieces to every other process 单个进程将数据分区然后将数据块发送到其他进程
  • Gather : A single process assembles the data from different process in a buffer 单个进程将来自不同进程的数据组装在缓冲区中

一、集体通信以及同步点

同步点:这意味着所有的进程在执行代码的时候必须首先都到达一个同步点才能继续执行后面的代码。

MPI 有一个特殊的函数来做同步进程的这个操作。

MPI_Barrier(MPI_Comm communicator)

在这里插入图片描述

注意:始终记得每一个你调用的集体通信方法都是同步的。也就是说,如果没法让所有进程都完成 MPI_Barrier,那么你也没法完成任何集体调用。如果你在没有确保所有进程都调用 MPI_Barrier的情况下调用了它,那么程序会空闲下来。

二、MPI_Bcast 广播

广播 (broadcast) 是标准的集体通信技术之一。一个广播发生的时候,一个进程会把同样一份数据传递给一个 communicator 里的所有其他进程。广播的主要用途之一是把用户输入传递给一个分布式程序,或者把一些配置参数传递给所有的进程。

在这里插入图片描述
广播可以使用 MPI_Bcast 来做到,函数声明是:

MPI_Bcast(void* data,int count,MPI_Datatype datatype,int root,MPI_Comm communicator)

尽管根节点和接收节点做不同的事情,它们都是调用同样的这个 MPI_Bcast 函数来实现广播。

  • 当根节点(在我们的例子是节点0)调用 MPI_Bcast 函数时, data 变量里的值会被发送到其他的节点上。
  • 当其他的节点调用 MPI_Bcast 时,data 变量会被赋值成从根节点接受到的数据。

2.1 使用MPI_SendMPI_Recv 来做广播

粗略看的话,似乎 MPI_Bcast 仅仅是在 MPI_SendMPI_Recv 基础上进行了一层包装。事实上,我们就可以自己来做这层封装。我们的函数叫做 my_bcast。它跟 MPI_Bcast 接受一样的参数,看起来像这样:

void my_bcast(void* data, int count, MPI_Datatype datatype, int root, MPI_Comm communicator){int rank;MPI_Comm_rank(communicator, &rank);int size;MPI_Comm_size(communicator, &size);if (rank == root){for (int i = 0; i < size; i++){if (i != rank){MPI_Send(data, count, datatype, i, 0, communicator);}}} else {MPI_Recv(data, count, datatype, root, 0, communicator, MPI_STATUS_IGNORE);}
}

这个函数的时间复杂度应该是O(n)的
在这里插入图片描述采用树算法的时间复杂度是O(logn)。

2.2 MPI_BcastMPI_Send 以及 MPI_Recv 的比较

在这里插入图片描述

2.3 Blocking or non-blocking ?

这里仅给出阻塞版本。but you just need to add the I to switch to non-blocking mode (eg MPI_Bcast will become MPI_Ibcast). non-blocking globals require the use of MPI_Wait and MPI_Test to be completed correctly.

三、MPI Scatter, Gather, and Allgather

两个额外的机制来补充集体通信的知识 - MPI_Scatter 以及 MPI_Gather。还会讲一个 MPI_Gather 的变体:MPI_Allgather

3.1 MPI_Scatter介绍

MPI_BcastMPI_Scatter 的主要区别很小但是很重要。

  • MPI_Bcast 给每个进程发送的是同样的数据,
  • MPI_Scatter 给每个进程发送的是一个数组的一部分数据。

在这里插入图片描述
尽管根进程(进程0)拥有整个数组的所有元素,MPI_Scatter 还是会把正确的属于进程0的元素放到这个进程的接收缓存中。

MPI_Scatter 函数的原型:

MPI_Scatter(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,int root,MPI_Comm communicator)
  1. send_data是在根进程上的一个数据数组
  2. send_countsend_datatype分别描述了发送给每个进程的数据数量和数据类型:
    如果send_count 是1,send_datatypeMPI_INT的话,进程0会得到数据里的第一个整数,以此类推。如果send_count是2的话,进程0会得到前两个整数,进程1会得到第三个和第四个整数,以此类推。在实践中,一般来说send_count会等于数组的长度除以进程的数量。除不尽怎么办?会在后面讲这个问题 。
  3. 函数定义里面接收数据的参数跟发送的参数几乎相同。recv_data 参数是一个缓存,它里面存了recv_countrecv_datatype数据类型的元素。
  4. rootcommunicator 分别指定开始分发数组的根进程以及对应的communicator。

3.2 MPI_Gather 的介绍

MPI_GatherMPI_Scatter 是相反的。MPI_Gather 从好多进程里面收集数据到一个进程上面而不是从一个进程分发数据到多个进程。这个机制对很多平行算法很有用,比如并行的排序和搜索。
在这里插入图片描述MPI_Scatter类似,MPI_Gather从其他进程收集元素到根进程上面。元素是根据接收到的进程的秩排序的。MPI_Gather的函数原型跟MPI_Scatter长的一样。

MPI_Gather(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,int root,MPI_Comm communicator)

MPI_Gather中,只有根进程需要一个有效的接收缓存。所有其他的调用进程可以传递NULLrecv_data

注意:别忘记recv_count参数是从每个进程接收到的数据数量,而不是所有进程的数据总量之和。这一点对MPI初学者来说经常容易搞错。

3.3 使用 MPI_ScatterMPI_Gather 来计算平均数

这段代码展示了如何使用MPI来把工作拆分到不同的进程上,每个进程对一部分数据进行计算,然后再把每个部分计算出来的结果汇集成最终的答案。程序步骤是:

  1. 在根进程(进程0)上生成一个充满随机数字的数组。
  2. 把所有数字用MPI_Scatter分发给每个进程,每个进程得到的同样多的数字。
  3. 每个进程计算它们各自得到的数字的平均数。
  4. 根进程收集所有的平均数,然后计算这个平均数的平均数,得出最后结果。
// Author: Wes Kendall
// Copyright 2012 www.mpitutorial.com
// This code is provided freely with the tutorials on mpitutorial.com. Feel
// free to modify it for your own use. Any distribution of the code must
// either provide a link to www.mpitutorial.com or keep this header intact.
//
// Program that computes the average of an array of elements in parallel using
// MPI_Scatter and MPI_Gather
//
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpi.h>
#include <assert.h>// Creates an array of random numbers. Each number has a value from 0 - 1
float *create_rand_nums(int num_elements) {float *rand_nums = (float *)malloc(sizeof(float) * num_elements);assert(rand_nums != NULL);int i;for (i = 0; i < num_elements; i++) {rand_nums[i] = (rand() / (float)RAND_MAX);}return rand_nums;
}// Computes the average of an array of numbers
float compute_avg(float *array, int num_elements) {float sum = 0.f;int i;for (i = 0; i < num_elements; i++) {sum += array[i];}return sum / num_elements;
}int main(int argc, char** argv) {if (argc != 2) {fprintf(stderr, "Usage: avg num_elements_per_proc\n");exit(1);}int num_elements_per_proc = atoi(argv[1]);// Seed the random number generator to get different results each timesrand(time(NULL));MPI_Init(NULL, NULL);int world_rank;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);int world_size;MPI_Comm_size(MPI_COMM_WORLD, &world_size);// Create a random array of elements on the root process. Its total// size will be the number of elements per process times the number// of processesfloat *rand_nums = NULL;if (world_rank == 0) {rand_nums = create_rand_nums(num_elements_per_proc * world_size);}// For each process, create a buffer that will hold a subset of the entire// arrayfloat *sub_rand_nums = (float *)malloc(sizeof(float) * num_elements_per_proc);assert(sub_rand_nums != NULL);// Scatter the random numbers from the root process to all processes in// the MPI worldMPI_Scatter(rand_nums, num_elements_per_proc, MPI_FLOAT, sub_rand_nums,num_elements_per_proc, MPI_FLOAT, 0, MPI_COMM_WORLD);// Compute the average of your subsetfloat sub_avg = compute_avg(sub_rand_nums, num_elements_per_proc);// Gather all partial averages down to the root processfloat *sub_avgs = NULL;if (world_rank == 0) {sub_avgs = (float *)malloc(sizeof(float) * world_size);assert(sub_avgs != NULL);}MPI_Gather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT, 0, MPI_COMM_WORLD);// Now that we have all of the partial averages on the root, compute the// total average of all numbers. Since we are assuming each process computed// an average across an equal amount of elements, this computation will// produce the correct answer.if (world_rank == 0) {float avg = compute_avg(sub_avgs, world_size);printf("Avg of all elements is %f\n", avg);// Compute the average across the original data for comparisonfloat original_data_avg =compute_avg(rand_nums, num_elements_per_proc * world_size);printf("Avg computed across original data is %f\n", original_data_avg);}// Clean upif (world_rank == 0) {free(rand_nums);free(sub_avgs);}free(sub_rand_nums);MPI_Barrier(MPI_COMM_WORLD);MPI_Finalize();
}

上面这段代码有点犯懒,我本人没有写,直接copy Wes Kendall的代码,我简单来讲讲这个代码的思路:

// 在根进程中,随机产生elements_per_proc * world_size个随机数字的数组
if (world_rank == 0) {rand_nums = create_rand_nums(elements_per_proc * world_size);
}// 创建一个根进程传给其他进程的子数组作为缓存区,子数组的长度为elements_per_proc
float *sub_rand_nums = malloc(sizeof(float) * elements_per_proc);// 创建好了子数组作为缓存区之后,根进程就开始分发数据了
MPI_Scatter(rand_nums, elements_per_proc, MPI_FLOAT, sub_rand_nums,elements_per_proc, MPI_FLOAT, 0, MPI_COMM_WORLD);// 将每个进程得到的子数组的元素去平均值
float sub_avg = compute_avg(sub_rand_nums, elements_per_proc);// 再创建一个缓存区用来存储其他进程上传它们计算得到的平均值
float *sub_avgs = NULL;
if (world_rank == 0) {sub_avgs = malloc(sizeof(float) * world_size);
}
// 根进程gather到了其他进程传过来的数据
MPI_Gather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT, 0,MPI_COMM_WORLD);// 再去计算这些平均数的平均数们就能得到总的平均数
if (world_rank == 0) {float avg = compute_avg(sub_avgs, world_size);
}

3.4 MPI_Allgather

上面两个用来操作多对一或者一对多通信模式,也就是说多个进程要么向一个进程发送数据,要么从一个进程接收数据。

MPI_Allgather却是多个元素到多个进程(也就是多对多通信模式)。

在这里插入图片描述函数原型:

MPI_Allgather(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,MPI_Comm communicator)

MPI_Allgather的方法定义跟MPI_Gather几乎一样,只不过MPI_Allgather不需要root这个参数来指定根节点。

四、并行排名

4.1 问题概述

在这里插入图片描述

4.2 并行排名API定义

在深入研究并行排名问题之前,首先确定函数的行为方式。

  1. 函数需要在每个进程上取一个数字,并返回其相对于所有其他进程中的数字的排名
  2. 需要正在使用的communicator
  3. 被排名的数字的数据类型

函数原型:

TMPI_Rank(void* send_data, //作为缓冲区void* recv_data, // send_datade 排名MPI_Datatype datatype,MPI_Comm comm)

4.3 解决并行排名问题

4.3.1 对所有进程中的数字进行排序

最简单的方法是将所有数字收集到一个进程中并对数字进行排序。gather_numbers_to_root 函数负责将所有数字收集到根进程(root process)。

// 为进程0的TMPI_Rank收集数字。为MPI的数据类型分配空间
// 对进程0返回 void * 指向的缓冲区
// 对所有其他进程返回NULL
void *gather_numbers_to_root(void *number, MPI_Datatype datatype,MPI_Comm comm) {int comm_rank, comm_size;MPI_Comm_rank(comm, &comm_rank);MPI_Comm_size(comm, &comm_size);// 在根进程上分配一个数组// 数组大小取决于所用的MPI数据类型int datatype_size;MPI_Type_size(datatype, &datatype_size);void *gathered_numbers;if (comm_rank == 0) {gathered_numbers = malloc(datatype_size * comm_size);}// 在根进程上收集所有数字MPI_Gather(number, 1, datatype, gathered_numbers, 1,datatype, 0, comm);return gathered_numbers;
}
  • gather_numbers_to_root 函数获取要收集的数字(即 send_data 变量)、数字的数据类型 datatypecomm 通讯器。
  • 根进程必须在此函数中收集 comm_size 个数字,因此它会分配 datatype_size * comm_size 长度的数组.
  • 这里通过使用新的MPI函数- MPI_Type_size 来收集datatype_size变量。

4.3.2 排序数字并维护所属

  • 在我们的排名函数中,排序数字不一定是难题。CPP中提供了许多排序算法(我们也可以自己写)
  • 排序的困难在于,我们必须维护各个进程将数字发送到根进程的次序。 如果我们要对收集到根进程的数组进行排序而不给数字附加信息,则根进程将不知道如何将数字的排名发送回原来请求的进程!
  • 为了便于将所属进程附到对应数字上,我们在代码中创建了一个结构体(struct)来保存此信息。
    该结构体定义如下:
// 保存进程在通讯器中的次序(rank)和对应数字
// 该结构体用于数组排序,
// 并同时完整保留所属进程信息typedef struct {int comm_rank;union {float f;int i;} number;
} CommRankNumber;

CommRankNumber 结构体保存了我们要排序的数字(记住它可以是浮点数或整数,因此我们使用联合体union),并且它拥有该数字所属进程在通讯器中的次序(rank)

get_ranks 函数,负责创建这些结构体并对它们进行排序。

// 这个函数在根进程上对收集到的数字排序
// 返回一个数组,数组按进程在通讯器中的次序排序
// 注意 - 该函数只在根进程上运行int *get_ranks(void *gathered_numbers, int gathered_number_count,MPI_Datatype datatype) {int datatype_size;MPI_Type_size(datatype, &datatype_size);// 将收集到的数字数组转换为CommRankNumbers数组// 这允许我们在排序的同时,完整保留数字所属进程的信息CommRankNumber *comm_rank_numbers = malloc(gathered_number_count * sizeof(CommRankNumber));int i;for (i = 0; i < gathered_number_count; i++) {comm_rank_numbers[i].comm_rank = i;memcpy(&(comm_rank_numbers[i].number),gathered_numbers + (i * datatype_size),datatype_size);}// 根据数据类型对comm_rank_numbers排序if (datatype == MPI_FLOAT) {qsort(comm_rank_numbers, gathered_number_count,sizeof(CommRankNumber), &compare_float_comm_rank_number);} else {qsort(comm_rank_numbers, gathered_number_count,sizeof(CommRankNumber), &compare_int_comm_rank_number);}// 现在comm_rank_numbers是排好序的,下面生成一个数组,// 包含每个进程的排名,数组第i个元素是进程i的数字的排名int *ranks = (int *)malloc(sizeof(int) * gathered_number_count);for (i = 0; i < gathered_number_count; i++) {ranks[comm_rank_numbers[i].comm_rank] = i;}// 清理并返回排名数组free(comm_rank_numbers);return ranks;
}
  • get_ranks 函数首先创建一个CommRankNumber结构体数组,并附上该数字所属进程在通讯器中的次序。 如果数据类型为 MPI_FLOAT ,则对我们的结构体数组调用 qsort 时,会使用特殊的排序函数。类似的,如果数据类型为 MPI_INT ,我们将使用不同的排序函数。

  • 在对数字进行排序之后,我们必须以适当的顺序创建一个排名数组(array of ranks),以便将它们分散(scatter)回到请求的进程中。这是通过创建 ranks 数组并为每个已排序的 CommRankNumber 结构体填充适当的排名来实现的。

4.3.3 整合

现在有了两个主要函数,可以将它们全部整合到 TMPI_Rank 函数中。此函数将数字收集到根进程,并对数字进行排序以确定其排名,然后将排名分散回请求的进程。 代码如下所示:

// 获取send_data的排名, 类型为datatype
// 排名用recv_data返回,类型为datatype
int TMPI_Rank(void *send_data, void *recv_data, MPI_Datatype datatype,MPI_Comm comm) {// 首先检查基本情况 - 此函数只支持MPI_INT和MPI_FLOATif (datatype != MPI_INT && datatype != MPI_FLOAT) {return MPI_ERR_TYPE;}int comm_size, comm_rank;MPI_Comm_size(comm, &comm_size);MPI_Comm_rank(comm, &comm_rank);// 为了计算排名,必须将数字收集到一个进程中// 对数字排序, 然后将排名结果分散传回// 首先在进程0上收集数字void *gathered_numbers = gather_numbers_to_root(send_data, datatype,comm);// 获取每个进程的次序(rank)int *ranks = NULL;if (comm_rank == 0) {ranks = get_ranks(gathered_numbers, comm_size, datatype);}// 分散发回排名结果MPI_Scatter(ranks, 1, MPI_INT, recv_data, 1, MPI_INT, 0, comm);// 清理if (comm_rank == 0) {free(gathered_numbers);free(ranks);}
}

TMPI_Rank 函数使用我们刚刚创建的两个函数 gather_numbers_to_rootget_ranks 来获取数字的排名。然后,函数执行最后的 MPI_Scatter,以将所得的排名分散传回进程。

4.4 最终结果

最终代码参看tutorials/performing-parallel-rank-with-mpi/code/tmpi_rank.c

以下是整个数据流说明:
在这里插入图片描述

五、MPI Reduce and Allreduce

5.1 归约/归化(reduce)简介

这个概念在OpenMPI中介绍过,详情参见C/C++实现高性能并行计算——2.使用OpenMP进行共享内存编程

5.2 MPI_Reduce

函数原型:

MPI_Reduce(void* send_data,void* recv_data,int count,MPI_Datatype datatype,MPI_Op op,int root,MPI_Comm communicator)
  1. send_data 参数是每个进程都希望归约的 datatype 类型元素的数组。
  2. recv_data 仅与具有 root 秩的进程相关。 recv_data 数组包含归约的结果,大小为sizeof(datatype)* count
  3. op 参数是希望应用于数据的操作。 MPI 包含一组可以使用的常见归约运算。 尽管可以定义自定义归约操作,但这里不作介绍。

MPI定义的归约操作包括:
在这里插入图片描述
在这里插入图片描述

5.3 使用MPI_Reduce计算均值

在 第三节 中,展示了使用 MPI_ScatterMPI_Gather 计算平均值。 使用 MPI_Reduce 可以简化上一节的代码。

float *rand_nums = NULL;
rand_nums = create_rand_nums(num_elements_per_proc);// Sum the numbers locally
float local_sum = 0;
int i;
for (i = 0; i < num_elements_per_proc; i++) {local_sum += rand_nums[i];
}// Print the random numbers on each process
printf("Local sum for process %d - %f, avg = %f\n",world_rank, local_sum, local_sum / num_elements_per_proc);// Reduce all of the local sums into the global sum
float global_sum;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_FLOAT, MPI_SUM, 0,MPI_COMM_WORLD);// Print the result
if (world_rank == 0) {printf("Total sum = %f, avg = %f\n", global_sum,global_sum / (world_size * num_elements_per_proc));
}
  1. 每个进程都会创建随机数并计算和保存在 local_sum
  2. 然后使用 MPI_SUMlocal_sum 归约至根进程。
  3. 全局平均值为 global_sum / (world_size * num_elements_per_proc)

5.4 MPI_Allreduce

如果所有进程而不是仅仅在根进程中访问归约的结果。 MPI_Allreduce 将归约值并将结果分配给所有进程。 函数原型如下:

MPI_Allreduce(void* send_data,void* recv_data,int count,MPI_Datatype datatype,MPI_Op op,MPI_Comm communicator)

在这里插入图片描述

5.4.1 使用 MPI_Allreduce 计算标准差

详情代码见tutorials/mpi-reduce-and-allreduce/code/reduce_stddev.c


总结

其实这篇文章到了后期我写的不是很满意,因为代码都是直接复制粘贴,不像之前的都是自己写再去对照别人的代码。可能是状态不好,也有可能是太急于求成了,罪过,罪过。太想进步啦!!!后期我可能会重新编辑这篇文章,代码我也要重新写一遍。

  1. 集体通信和同步点
  2. 广播MPI_Bcast
  3. 发散MPI_Scatter和收集MPI_Gather(所有进程都要一份)MPI_Allgather
  4. 归约/归化 MPI_Reduce,所有进程都要一份归化MPI_Allreduce

参考

  1. MPI Tutorials

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

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

相关文章

2024美国虚拟信用卡申请流程

一、消费场景 二、如果申请 Fomepay美国虚拟信用卡 1.打开 Fomepay官方网站地址 2、登录之后根据自己的需求选择卡bin 3、点击申请卡&#xff0c;选择金额、填写姓名&#xff0c;选择微信/支付宝点击确认开卡即可 记得刷新页面哦~~~~~ 卡信息在卡中心cvc安全码里面 4、虚拟信…

一站式PDF解决方案:如何部署自己的PDF全能工具(Docker部署和群晖部署教程)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 开始部署 📒📝 Docker部署📝 群晖部署📝 本地安装⚓️ 相关链接 ⚓️📖 介绍 📖 在数字化办公的今天,PDF文件几乎成了我们日常工作中不可或缺的一部分。但你是否曾因为PDF文件的编辑、转换、合并等问题而头疼?如果…

面向对象设计之套路——设计模式

1、总则 面向对象的分析设计编程思想&#xff0c;通过封装、继承、多态把程序的耦合度降低&#xff0c;用设计模式使得程序更加灵活&#xff0c;容易修改&#xff0c;并且易于复用。 让业务逻辑与界面逻辑分开&#xff0c;让它们的耦合度下降&#xff0c;只有分离&#xff0c;…

气膜体育馆:有效防范PM2.5—轻空间

在现代城市生活中&#xff0c;雾霾天气频发&#xff0c;PM2.5污染日益严重&#xff0c;给人们的健康和生活带来了不小的困扰。而气膜体育馆作为一种新型的运动场所&#xff0c;不仅能够解决户外运动受到天气影响的问题&#xff0c;还具备有效防范PM2.5的功能。轻空间将带您探讨…

在centos7中运行向量数据库PostgreSQL连接不上如何排查?

1. 检查 PostgreSQL 服务状态 首先&#xff0c;您需要确认 PostgreSQL 服务是否正在运行。您可以使用以下命令来检查服务状态&#xff1a; sudo systemctl status postgresql如果服务没有运行&#xff0c;您需要启动它&#xff1a; sudo systemctl start postgresql2. 确认 …

力扣每日一题- 给植物浇水 II -2024.5.9

力扣题目&#xff1a;给植物浇水 II 题目链接: 2105.给植物浇水 II 题目描述 代码思路 根据题目内容&#xff0c;使用双指针从左右两边同时向中间移动&#xff0c;模拟浇水过程即可。 代码纯享版 class Solution {public int minimumRefill(int[] plants, int capacityA, …

永倍达 最新消息!发放消费券! 重新开网?

大家好 我是一家软件开发公司的产品经理 吴军 我又又又又又叕来蹭热度了&#xff0c;最近永倍达有新动作&#xff01;发放消费券&#xff1f; 店长群最新通知 4.15号开始发放消费券 一个月之后才可以重新提现 今天是5.10号离5.18也不远了 大家拭目以待看看到底能不能提现&a…

数据库调优-连接池优化

先贴下连接池的相关配置&#xff1a; 连接池参数配置&#xff1a; 字段含义Max Number of Connections最大连接数&#xff1b;做性能测试时&#xff0c;可以填 0 。在开发的项目中按实际代码填写&#xff0c;默认是 20 。Max Wait(ms)在连接池中取回连接最大等待时间&#xf…

在线旅游网站,基于 SpringBoot+Vue+MySQL 开发的前后端分离的在线旅游网站设计实现

目录 一. 前言 二. 功能模块 2.1. 登录界面 2.2. 管理员功能模块 2.3. 用户功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff0c;旅游网站当然…

QT+多线程TCP服务器+进阶版

针对之前的服务器&#xff0c;如果子线程工作类里面需要使用socket发送消息&#xff0c;必须要使用信号与槽的方法&#xff0c; 先发送一个信号给父进程&#xff0c;父进程调用socket发送消息&#xff08;原因是QT防止父子进程抢夺同一资源&#xff0c;因此直接规定父子进程不能…

完整性验证器:迈向 Starknet 超高可扩展性的一大步

原文&#xff1a;https://www.starknet.io/en/content/the-integrity-verifier-a-leap-toward-starknet-hyperscaling&#xff1b;https://www.starknet.io/en/ecosystem/grant 编译&#xff1a;TinTinLand 核心观点 由 Herodotus 开发的完整性验证器&#xff0c;使开发者能够…

【LeetCode算法】28. 找出字符串中第一个匹配项的下标

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案四、JAVA截取字符串的常用方法4.1 通过subString()截取字符串* 一、题目 给你两个字符串 haystack 和 needle &#xff0c;请你在…

QT切换控件布局

1、切换前垂直布局 2、切换后水平布局 3、关键代码 qDebug() << "开始切换布局";QWidget *widget centralWidget();QLayout *layout widget->layout();if(layout){while(layout->count()){QLayoutItem *item layout->takeAt(0);if(item->layout…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

Linux的命令(第二篇)

昨天学习到了第17个命令到 rm 命令&#xff08;作用删除目录和文件&#xff09;&#xff0c;今天继续往下里面了解其他命令以及格式、选项&#xff1a; &#xff08;17&#xff09;wc命令&#xff08;此wc非wc&#xff09; 作用&#xff1a;统计行数、单词数、字符分数。 格…

显影不干净如何解决?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;光刻工序完成后&#xff0c;晶圆表面有部分图形容易出现显影不净是什么原因&#xff1f;有什么好的解决办法吗&#xff1f; 光刻工序流程 …

武汉星起航:新手亚马逊开店全攻略,轻松上手,实现电商梦想!

随着全球电商市场的蓬勃发展&#xff0c;越来越多的创业者选择加入亚马逊这一国际电商平台&#xff0c;开启自己的电商之旅。然而&#xff0c;对于新手来说&#xff0c;如何在亚马逊上开店并赚取稳定收入&#xff0c;无疑是一项充满挑战的任务。武汉星起航在这里整理了一份完整…

Panasonic机器人维修|松下机械手维修过程

在我们的科技日新月异的今天&#xff0c;松下机器人已经广泛应用于各个领域&#xff0c;发挥着越来越重要的作用。然而&#xff0c;这些Panasonic机械手维修过程也是一项重要且复杂的工作。 一、准备工作 在进行松下机器人维修前&#xff0c;需要充分了解机器人的构造和工作原理…

泰达克仿钻点水晶饰品包装印刷防滑UV胶特性及应用场景

仿钻点UV滴胶是一种特殊的胶水 常用于模拟钻石的效果 它是一种透明的胶水 具有高光泽度和折射率 可以在物体表面形成类似钻石的亮闪效果 仿钻点UV滴胶通常由紫外线固化胶组成 需要通过紫外线照射来固化和硬化 它具有以下特点&#xff1a; 1. 透明度&#xff1a;仿钻点UV滴胶具有…

LaTeX公式学习笔记

\sqrt[3]{100} \frac{2}{3} \sum_{i0}^{n} x^{3} \log_{a}{b} \vec{a} \bar{a} \lim_{x \to \infty} \Delta A B C \alpha αΑ\xiξ\XiΞ\beta βΒ\pi π\PiΠ\gamma γ\GammaΓ\varpiϖ\delta δ\DeltaΔ\rhoρΡ\epsilon ϵΕ\varrho ϱ\varepsilo…