矩阵乘法(C++ mpi 并行实现)

news2024/11/16 23:56:32

矩阵乘法有2种思路,我最先想到的是第一种思路,但是时间、空间复杂度都比较高。后面参考了一些资料,实现了第二种思路。

一、思路1:按行、列分块

矩阵乘法有一个很好的性质,就是结果矩阵的每个元素是不互相依赖的,因此我们可以很好地实现并行。

假如想要计算的矩阵如下:

在这里插入图片描述

如果有8个进程,0号进程用于数据分发,其它进程用于计算,具体的分块如下图所示:

在这里插入图片描述

但是这种思路实现后会报错 Out of memory,具体原因后面分析。

在这里插入图片描述

思路1因为只传递了单行的数组,而二维vector单行数组是连续的,因此这里直接用二维vector表示矩阵就可以,代码如下:

#include <stdio.h>
#include <mpi.h>
#include<iostream>
#include<vector>
#include <cstdlib>
using namespace std;

int main(int argc, char* argv[])

{
    int myrank, processNum; // myrank: 进程号; processNum: 进程总数
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    int namelen;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
    MPI_Comm_size(MPI_COMM_WORLD, &processNum);
    MPI_Get_processor_name(processor_name, &namelen); // 获取处理器名称

    double startTime, endTime; // 开始时间和结束时间

    // 如果是 0 号进程,随机初始化矩阵数据
    int row = 3000, middleColumn = 200, column = 300; // 矩阵 A 的行数、矩阵 A 的列数和 B 的行数、矩阵 B 的行数
    if (myrank == 0) { // 地主进程
        vector<vector<int>> A(row, vector<int>(middleColumn));
        vector<vector<int>> B(middleColumn, vector<int>(column));
        // 随机初始化矩阵 A 和 B
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < middleColumn; j++) {
				A[i][j] = rand() % 10;
			}
		}
        for (int i = 0; i < middleColumn; i++) {
            for (int j = 0; j < column; j++) {
                B[i][j] = rand() % 10;
            }
        }

        // 打印矩阵 A 和 B
  //      cout << "A:" << endl;
  //      for (int i = 0; i < row; i++) {
		//	cout << "    ";
  //          for (int j = 0; j < middleColumn; j++) {
		//		cout << A[i][j] << " ";
		//	}
		//	cout << endl;
		//}

  //      cout << "B:" << endl;
  //      for (int i = 0; i < middleColumn; i++) {
  //          cout << "    ";
  //          for (int j = 0; j < column; j++) {
		//		cout << B[i][j] << " ";
		//	}
  //          cout << endl;
  //      }


        startTime = MPI_Wtime(); // 开始计时
        int farmersProcessNum = processNum - 1; // 农民进程数
        // 分发矩阵 A 和 B
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                int destProcess = (i * row + j) % farmersProcessNum + 1; // 目标进程号
                MPI_Send(A[i].data(), middleColumn, MPI_INT, destProcess, 0, MPI_COMM_WORLD);
                vector<int> BColumn(middleColumn);
                for (int k = 0; k < middleColumn; k++) {
					BColumn[k] = B[k][j];
				}
                MPI_Send(BColumn.data(), middleColumn, MPI_INT, destProcess, 0, MPI_COMM_WORLD);
            }
        }
    }
    else { // 农民进程 (进程号 > 0 的进程)
        int count = (row * column) / (processNum - 1);      // 每个进程计算的数量
        int remainder = (row * column) % (processNum - 1);  // 均分后多出来的待计算数
        if (myrank <= remainder) {
			count++;
		}
        
        for (int i = 0; i < count; i++) {
            // 接收矩阵 ARow 和 BColumn
            vector<int> ARow(middleColumn);
            vector<int> BColumn(middleColumn);
            MPI_Recv(ARow.data(), middleColumn, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            MPI_Recv(BColumn.data(), middleColumn, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            // 计算矩阵乘法
            int result = 0;
            for (int j = 0; j < middleColumn; j++) {
                result += ARow[j] * BColumn[j];
            }
            // 发送计算结果
            MPI_Send(&result, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
        }
    }

    // 收集计算结果
    if (myrank == 0) {
		vector<vector<int>> C(row, vector<int>(column));
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                // 计算农民进程的数量
                int farmersProcessNum = processNum - 1;
                // 计算接收的进程号
                int sourceProcess = (i * row + j) % farmersProcessNum + 1;
                MPI_Recv(&C[i][j], 1, MPI_INT, sourceProcess, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            }
        }
        endTime = MPI_Wtime(); // 结束计时

        // 打印矩阵 C
        //cout << "C:" << endl;
        //for (int i = 0; i < row; i++) {
        //    cout << "    ";
        //    for (int j = 0; j < column; j++) {
        //        cout << C[i][j] << " ";
        //    }
        //    cout << endl;
        //}

        // 打印计算时间
        cout << "Time: " << endTime - startTime << "s" << endl;
    }


    MPI_Finalize();

    return 0;
}

二、思路2:矩阵分块乘法

如果我们想要计算的矩阵如下:

在这里插入图片描述

同时,如果我们采用4个进程来计算这个矩阵乘法,0号进程用于数据分发,其它进程用于计算,如图:

在这里插入图片描述

1维数组表示2维数组

思路2的算法实现,由于我们进行MPI_Send发送数组的时候,必须提供一个连续地址的数组的首地址,而思路2不同于思路1只发送1行数据,它是要发送多行的。

这就要求我们不可以使用二维数组来实现,因为二维数组不同行的首尾地址并不是连续的。具体见这篇文章:https://blog.csdn.net/weixin_44560698/article/details/120566680.

所以,这里我们使用1维数组,表示2维数组,代码如下:

#include<iostream>
#include<vector>
#include<mpi.h>

using namespace std;

// 输入:两个一维vector,表示矩阵,输出矩阵的row,middleColumn,column
// 输出:一维vector,表示两个输入vector的乘积
vector<int> matrixMultiplication(vector<int> A, vector<int> B, int row, int middleColumn, int column) {
	vector<int> res(row * column);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < column; j++) {
			int tmp = 0;
			for (int k = 0; k < middleColumn; k++) {
				// A[i][k] * B[k][j]
				tmp += A[i * middleColumn + k] * B[k * column + j];
			}
			// res[i][j] = tmp
			res[i * column + j] = tmp;
		}
	}
	return res;
}


int main(int argc, char* argv[])

{

	int myrank, processNum;
	char processor_name[MPI_MAX_PROCESSOR_NAME];
	int namelen;

	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
	MPI_Comm_size(MPI_COMM_WORLD, &processNum);
	MPI_Get_processor_name(processor_name, &namelen);

	// 如果是 0 号进程,随机初始化矩阵数据
	int row = 3000, middleColumn = 2000, column = 3000;
	int farmerProcessNum = processNum - 1; // farmer 进程的数量
	int chunkSize = row / farmerProcessNum; // 每个 farmer 进程处理的行数
	int chunkSizeRemainder = row % farmerProcessNum; // 均分后的余数
	double startTime, endTime; // 记录开始和结束时间
	if (myrank == 0) { // 地主进程
		vector<int> A(row * middleColumn);
		vector<int> B(middleColumn * column);
		// 随机初始化矩阵 A 和 B
		for (int i = 0; i < row * middleColumn; i++) {
			A[i] = rand() % 10;
		}
		for (int i = 0; i < middleColumn * column; i++) {
			B[i] = rand() % 10;
		}

		// 打印矩阵 A 和 B
		//cout << "A:" << endl;
		//for (int i = 0; i < row; i++) {
		//	cout << "    ";
		//	for (int j = 0; j < middleColumn; j++) {
		//		cout << A[i * middleColumn + j] << " ";
		//	}
		//	cout << endl;
		//}

		//cout << "B:" << endl;
		//for (int i = 0; i < middleColumn; i++) {
		//	cout << "    ";
		//	for (int j = 0; j < column; j++) {
		//		cout << B[i * column + j] << " ";
		//	}
		//	cout << endl;
		//}

		// 记录程序开始时间
		startTime = MPI_Wtime();

		int startRow = 0, endRow = 0; // 每个 farmer 进程处理的起始行和结束行

		// 将矩阵 A 和 B 分发给其他进程
		for (int i = 1; i < processNum; i++) {
			// 计算下一个进程处理的结束行
			endRow = startRow + chunkSize - 1;
			if (i <= chunkSizeRemainder) {
				endRow++;
			}

			int tmpRow = endRow - startRow + 1;
			// 将矩阵 A 部分的数据分发给其他进程
			MPI_Send(A.data() + startRow * middleColumn, tmpRow * middleColumn, MPI_INT, i, 0, MPI_COMM_WORLD);
			// 将矩阵 B 所有的数据分发给其他进程
			MPI_Send(B.data(), middleColumn * column, MPI_INT, i, 0, MPI_COMM_WORLD);
			// 更新下一个进程处理的起始行
			startRow = endRow + 1;
		}
	}
	else { // 农民进程 (进程号 >= 1)
		// 计算行数
		int tmpRow = chunkSize;

		if (myrank <= chunkSizeRemainder) {
			tmpRow++;
		}

		// 计算列数
		int tmpColumn = column;
		// 计算中间列数
		int tmpMiddleColumn = middleColumn;

		// 接收矩阵 A 的数据
		vector<int> A(tmpRow * tmpMiddleColumn);
		MPI_Recv(A.data(), tmpRow * tmpMiddleColumn, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

		// 接收矩阵 B 的数据
		vector<int> B(tmpMiddleColumn * tmpColumn);
		MPI_Recv(B.data(), tmpMiddleColumn * tmpColumn, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

		// 计算矩阵乘法
		vector<int> res = matrixMultiplication(A, B, tmpRow, tmpMiddleColumn, tmpColumn);

		// 将计算结果返回给 0 号进程
		MPI_Send(res.data(), tmpRow * tmpColumn, MPI_INT, 0, 0, MPI_COMM_WORLD);
	}

	// 0 号进程接收其他进程的计算结果
	if (myrank == 0) {
		vector<int> res(row * column);
		int startRow = 0, endRow = 0; // 每个 farmer 进程处理的起始行和结束行
		for (int i = 1; i < processNum; i++) {
			// 计算下一个进程处理的结束行
			endRow = startRow + chunkSize - 1;
			if (i <= chunkSizeRemainder) {
				endRow++;
			}
			int tmpRow = endRow - startRow + 1;
			// 将矩阵 A 部分的数据分发给其他进程
			MPI_Recv(res.data() + startRow * column, tmpRow * column, MPI_INT, i, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
			// 更新下一个进程处理的起始行
			startRow = endRow + 1;
		}

		// 记录程序结束时间
		endTime = MPI_Wtime();
		
		// 打印计算结果
		//cout << "res:" << endl;
		//for (int i = 0; i < row; i++) {
		//	cout << "    ";
		//	for (int j = 0; j < column; j++) {
		//		cout << res[i * column + j] << " ";
		//	}
		//	cout << endl;
		//}

		// 打印计算时间
		cout << "Time: " << endTime - startTime << "s" << endl;
	}

	MPI_Finalize();

	return 0;

}

三、算法性能对比

1. 时间复杂度

思路一 300 × 200 300\times 200 300×200 200 × 300 200 \times 300 200×300 相乘所耗费的时间为8.83s。

在这里插入图片描述

相同规模的矩阵相乘,思路2的时间是0.07s。

在这里插入图片描述

可以看到差距还是很大的。

我们再来看一下串行程序的运行时间:

在这里插入图片描述
串行程序的运行时间是0.321s,看来如果我们采用思路一的并行方法就真有点得不偿失了。

这里给出我写的串行代码:

#include<iostream>
#include<vector>

using namespace std;

// 输入:两个一维vector,表示矩阵,输出矩阵的row,middleColumn,column
// 输出:一维vector,表示两个输入vector的乘积
vector<int> matrixMultiplication(vector<int> A, vector<int> B, int row, int middleColumn, int column) {
	vector<int> res(row * column);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < column; j++) {
			int tmp = 0;
			for (int k = 0; k < middleColumn; k++) {
				// A[i][k] * B[k][j]
				tmp += A[i * middleColumn + k] * B[k * column + j];
			}
			// res[i][j] = tmp
			res[i * column + j] = tmp;
		}
	}
	return res;
}

int main() {
	// 矩阵 A 的行数、矩阵 A 的列数和 B 的行数、矩阵 B 的行数
	int row = 300, middleColumn = 200, column = 300;
	vector<int> A(row * middleColumn);
	vector<int> B(middleColumn * column);
	// 随机初始化矩阵 A 和 B
	for (int i = 0; i < row * middleColumn; i++) {
		A[i] = rand() % 10;
	}
	for (int i = 0; i < middleColumn * column; i++) {
		B[i] = rand() % 10;
	}
	// 记录开始和结束时间
	double startTime, endTime;
	startTime = clock();
	vector<int> res = matrixMultiplication(A, B, row, middleColumn, column);
	endTime = clock();
	cout << "Time: " << (endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
	// 输出 res
	//for (int i = 0; i < row; i++) {
	//	for (int j = 0; j < column; j++) {
	//		cout << res[i * column + j] << " ";
	//	}
	//	cout << endl;
	//}
	//cout << endl;
}

我们将矩阵的大小扩大10倍,再来看:

思路1运行时间:

在这里插入图片描述

可以看到思路1直接out of memory了,其实是在意料之中的,具体原因可以看文章最后一部分 四、复杂度分析

思路2运行时间:

在这里插入图片描述

串行运行时间:

在这里插入图片描述

2. 空间复杂度

四、复杂度分析

首先,我们设:

r o w : 矩阵 A 行数 row:矩阵A行数 row:矩阵A行数

m i d d l e C o l u m n : 矩阵 A 列数和矩阵 B 行数 middleColumn:矩阵A列数和矩阵B行数 middleColumn:矩阵A列数和矩阵B行数

c o l u m n : 矩阵 B 列数 column:矩阵B列数 column:矩阵B列数

f a r m e r P r o c e s s N u m : 农民进程数 farmerProcessNum:农民进程数 farmerProcessNum:农民进程数

由于mpi主要是在并行程序之间进行通信。

对于思路1,是每次计算结果矩阵的一个元素,因此,它的总发送的数据量如下:

r o w × c o l u m n × m i d d l e C o l u m n 2 ( i n t ) row\times column \times middleColumn^2\quad (int) row×column×middleColumn2(int)

对于思路2,总发送的是A矩阵的所有+B矩阵的全部乘以进程数(因为每次都要发送B矩阵),如下:

r o w × m i d d l e C o l u m n + f a r m e r P r o c e s s N u m × m i d d l e C o l u m n × c o l u m n row\times middleColumn+farmerProcessNum\times middleColumn\times column row×middleColumn+farmerProcessNum×middleColumn×column

我们带入 r o w = 3000 , m i d d l e C o l u m n = 2000 , c o l u m n = 3000 row=3000,middleColumn=2000,column=3000 row=3000,middleColumn=2000,column=3000来计算一下上述思路1需要传输的内存:
r o w × c o l u m n × m i d d l e C o l u m n 2 ( i n t ) = 3000 × 3000 × 2000 × 2000 ( i n t ) = 36 0000 0000 0000 ( i n t ) = 4 × 36 0000 0000 0000 ( B y t e ) ≈ 131 ( T B ) \begin{aligned} &row\times column \times middleColumn^2\quad (int) \\ =&3000\times 3000 \times 2000\times 2000\quad (int) \\ =&36\quad 0000\quad 0000\quad 0000\quad (int) \\ =&4\times 36 \quad 0000\quad 0000\quad 0000\quad (Byte)\\ \approx&131 \quad (TB) \end{aligned} ===row×column×middleColumn2(int)3000×3000×2000×2000(int)36000000000000(int)4×36000000000000(Byte)131(TB)

具体转化结果如下:
在这里插入图片描述

传输的数据不管是传给哪个进程都是要放在内存里的,因此这种方式的空间数量级根本不可取= =。

再来看下思路2:

r o w × m i d d l e C o l u m n + f a r m e r P r o c e s s N u m × m i d d l e C o l u m n × c o l u m n ( i n t ) = 3000 × 2000 + 7 × 2000 × 3000 ( i n t ) = 4800 0000 ( i n t ) = 19200 0000 ( B y t e ) \begin{aligned} &row\times middleColumn+farmerProcessNum\times middleColumn\times column \quad (int) \\ =&3000\times 2000+7 \times 2000 \times 3000 \quad (int) \\ =&4800\quad 0000 \quad (int) \\ =&19200 \quad 0000 \quad (Byte) \end{aligned} ===row×middleColumn+farmerProcessNum×middleColumn×column(int)3000×2000+7×2000×3000(int)48000000(int)192000000(Byte)

在这里插入图片描述

可以看到 思路2 消耗的内存仅有 183MB ,与 思路1131TB 的空间复杂度完全不在一个数量级。

结论:所以对于矩阵并行乘法,思路1是不可行的,至少也是应该是采用思路2来进行实现的。

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

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

相关文章

如何批量加密PDF文件并设置不同密码 - 批量PDF加密工具使用教程

如果你正在寻找一种方法来批量加密和保护你的PDF文件&#xff0c;批量PDF加密工具是一个不错的选择。 它是一个体积小巧但功能强大的Windows工具软件&#xff0c;能够批量给多个PDF文件加密和限制&#xff0c;包括设置打印限制、禁止文字复制&#xff0c;并增加独立的打开密码。…

React实战 - React路由鉴权

目录 一、React-Router知识回顾 二、路由鉴权应用分析 三、路由鉴权配置 四、权限控制 一、React-Router知识回顾 React-router相关的文章中我已经给大家演示了最基础的应用&#xff1a; <Switch ><Route path"/products/:id" component{ProductDetai…

【Rust】Rust学习 第十七章Rust 的面向对象特性

面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是一种模式化编程方式。对象&#xff08;Object&#xff09;来源于 20 世纪 60 年代的 Simula 编程语言。这些对象影响了 Alan Kay 的编程架构中对象之间的消息传递。他在 1967 年创造了 面向对…

Blob,File文件上传下载的内容笔记

Blob 对象表示一个不可变、原始数据的类文件对象&#xff0c;可以看做是存放二进制数据的容器 。 简单来说Blob就是一个二进制的对象&#xff0c;我们可以通过这个blob对象直接读取文件内容 Blob和Flie没什么区别&#xff0c;File继承于Blob,就是多了一个name属性&#xff0c;表…

当今职场,正在加速淘汰 “巨婴员工”

我担任过多家上市公司的技术高管职位&#xff0c;在工作中经常会遇到巨婴型员工&#xff0c;他们外在的表现是&#xff0c;不能够很好地管理自己&#xff0c;缺乏自律&#xff0c;缺乏起码的抗挫折能力和抗压能力&#xff0c;需要领导呵护着、同事们忍让着。作为一名管理者&…

科技成果鉴定测试有什么意义?专业CMA、CNAS软件测评公司

科技成果鉴定测试是指通过一系列科学的实验和检测手段&#xff0c;对科技成果进行客观评价和鉴定的过程。通过测试&#xff0c;可以对科技成果的技术优劣进行评估&#xff0c;从而为科技创新提供参考和指导。 一、科技成果鉴定测试的意义 1、帮助客户了解科技产品的性能特点和…

排序(七种排序)

1.插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 1.插入排序 1.1思路 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为 止&#xff0c;得到一个新的有序序列 1.2实现 //插入排…

[计算机入门] 个性化设置系统

3.2 个性化设置系统 在Windows系统中&#xff0c;个性化设置可以让用户根据自己的喜好和需求对系统进行定制和调整&#xff0c;包括桌面背景、声音、屏幕保护程序、鼠标指针、字体等。通过个性化设置&#xff0c;用户可以创建自己的独特界面和用户体验&#xff0c;使系统更加符…

HBuilderX获取iOS证书的打包步骤

简介&#xff1a; 目前app开发&#xff0c;很多企业都用H5框架来开发&#xff0c;而uniapp又是这些h5框架里面最成熟的&#xff0c;因此hbuilderx就成为了开发者的首选。然而,打包APP是需要证书的&#xff0c;那么这个证书又是如何获得呢&#xff1f; 生成苹果证书相对复杂一些…

Hyper-V Linux服务器安装

官方文档&#xff1a;在 Windows 10 创意者更新上使用 Hyper-V 创建虚拟机 | Microsoft Learn 1 新增虚拟交换机 打开Hyper-V管理器&#xff0c;找到右侧的操作列&#xff0c;点击“虚拟交换机管理器”&#xff1a; 点击“新建虚拟网络交换机”&#xff0c;交换机类型选择“外部…

用idea解决代码合并冲突

参考文章&#xff1a; IDEA&#xff1a;idea中的Git冲突解决&#xff08;非常重要&#xff09; idea操作git时 合并分支解决冲突 一、前言 1.什么事冲突&#xff1f; 冲突是指当你在提交或者更新代码时被合并的文件与当前文件不一致。读起来有点绕&#xff0c;结合下面的案例…

16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及FileSystem示例(1)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

Unity解决:3D开发模式第三人称视角 WASD控制角色移动旋转 使用InputSystem

Unity版本&#xff1a;2019.2.3f1 目录 安装InputSystem 1&#xff1a;创建InputHander.cs脚本 挂载到Player物体上 获取键盘输入WADS 2.创建PlayerLocomotion.cs挂载到Player物体上&#xff0c;控制物体移动转向 安装InputSystem 菜单栏/Window/Package Manager/Input Syst…

AI图片处理功能演示

例如&#xff0c;这是一张不错的图片&#xff0c;但是有3只手。 我们可以选择有问题的区域&#xff0c;然后要求 niji 进行重新绘制。 根据我们选择的区域&#xff0c;我们可以以不同的方式修复结果。 创意修复 修复并不仅限于纠正错误。我们可以要求 niji 添加额外的元素&…

MATLAB R2023a for Mac Update_5

MATLAB是一种高级的计算机编程环境和开发工具&#xff0c;主要用于数值计算、数据分析、算法开发和可视化。它由MathWorks公司开发&#xff0c;被广泛应用于科学研究、工程设计、数据分析和教育等领域。 MATLAB提供了丰富的数学和工程函数库&#xff0c;可以进行矩阵运算、信号…

跨域资源共享 (CORS) | PortSwigger(burpsuite官方靶场)【万字】

写在前面 在开始之前&#xff0c;先要看看ajax的局限性和其他跨域资源共享的方式&#xff0c;这里简单说说。 下面提到大量的origin&#xff0c;注意区分referer&#xff0c;origin只说明请求发出的域。 浏览器的同源组策略&#xff1a;如果两个 URL 的 protocol、port 和 h…

所见即所得,「Paraverse平行云」助力万间打造智能建造新图景

在城市建设行业中&#xff0c;数字化逐渐成为其主导力量。 新一代信息基础设施建设也迎来了新的里程碑。数据显示&#xff0c;截至今年&#xff0c;我国已全面推进城市信息模型&#xff08;CIM&#xff09;基础平台建设&#xff0c;为城市规划、建设管理提供了多场景应用的强大…

【Python】代理池针对ip拦截破解

代理池是一种常见的反反爬虫技术&#xff0c;通过维护一组可用的代理服务器&#xff0c;来在被反爬虫限制的情况下&#xff0c;实现数据的爬取。但是&#xff0c;代理池本身也面临着被目标网站针对ip进行拦截的风险。 本文将详细介绍代理池针对ip拦截破解的方法&#xff0c;包含…

小研究 - Android 字节码动态分析分布式框架(三)

安卓平台是个多进程同时运行的系统&#xff0c;它还缺少合适的动态分析接口。因此&#xff0c;在安卓平台上进行全面的动态分析具有高难度和挑战性。已有的研究大多是针对一些安全问题的分析方法或者框架&#xff0c;无法为实现更加灵活、通用的动态分析工具的开发提供支持。此…

colab释放GPU显存

不用其他博客说的安装包&#xff0c;然后查看进程&#xff0c;kill&#xff0c;本文介绍一种简单的方法。 点击运行过代码的ipynb页面右上角的下三角&#xff0c;然后点击展开菜单栏中的View resources 随后会展开一个侧边栏&#xff0c;点击 manage sessions 3. 在页面中央会…