CUDA从入门到放弃(十四):CUDA Thrust库

CUDA从入门到放弃(十四):CUDA Thrust库

Thrust 是一个基于标准模板库(STL)的 C++ 模板库,专为 CUDA 设计,旨在简化高性能并行应用的开发。它提供了一系列数据并行原语,如扫描、排序和归约,可组合实现复杂算法。通过高级抽象描述计算,Thrust 能自动选择最优实现,适用于 CUDA 应用的快速原型设计和生产环境,提高程序员生产率和性能。

1 安装

安装 CUDA Toolkit 会将 Thrust 的头文件复制到您系统的标准 CUDA 包含目录中。由于 Thrust 是一个由头文件组成的模板库,因此无需进行其他安装步骤即可开始使用 Thrust。

2 Vectors

Thrust 提供了 host_vector 和 device_vector 两种向量容器,分别用于主机和 GPU 设备内存。它们类似于 C++ STL 中的 std::vector,是泛型容器,可动态调整大小。使用 = 运算符可以轻松地复制容器内容。device_vector 的元素可以通过标准括号访问,但应谨慎使用,因为每次访问都涉及内存复制。

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>#include <iostream>int main(void)
{// H has storage for 4 integersthrust::host_vector<int> H(4);// initialize individual elementsH[0] = 14;H[1] = 20;H[2] = 38;H[3] = 46;// H.size() returns the size of vector Hstd::cout << "H has size " << H.size() << std::endl;// print contents of Hfor(int i = 0; i < H.size(); i++)std::cout << "H[" << i << "] = " << H[i] << std::endl;// resize HH.resize(2);std::cout << "H now has size " << H.size() << std::endl;// Copy host_vector H to device_vector Dthrust::device_vector<int> D = H;// elements of D can be modifiedD[0] = 99;D[1] = 88;// print contents of Dfor(int i = 0; i < D.size(); i++)std::cout << "D[" << i << "] = " << D[i] << std::endl;// H and D are automatically deleted when the function returnsreturn 0;
}

Thrust 还提供了初始化向量元素和复制特定值集的方法。

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>#include <thrust/copy.h>
#include <thrust/fill.h>
#include <thrust/sequence.h>#include <iostream>int main(void)
{// initialize all ten integers of a device_vector to 1thrust::device_vector<int> D(10, 1);// set the first seven elements of a vector to 9thrust::fill(D.begin(), D.begin() + 7, 9);// initialize a host_vector with the first five elements of Dthrust::host_vector<int> H(D.begin(), D.begin() + 5);// set the elements of H to 0, 1, 2, 3, ...thrust::sequence(H.begin(), H.end());// copy all of H back to the beginning of Dthrust::copy(H.begin(), H.end(), D.begin());// print Dfor(int i = 0; i < D.size(); i++)std::cout << "D[" << i << "] = " << D[i] << std::endl;return 0;
}

2-1 Thrust 命名空间

Thrust 命名空间允许我们调用特定的函数或类,如 thrust::host_vector 和 thrust::copy,从而避免与其他库中的函数或类名称冲突。

2-2 迭代器与静态分发 Iterators and Static Dispatching

在 Thrust 中,迭代器类似于指针,用于访问容器中的元素。Thrust 函数通过检查迭代器的类型,自动决定使用主机还是设备实现,这个过程称为静态分发,它在编译时确定,没有运行时开销。

原始指针和device_ptr 互相转换:
如果使用原始指针作为参数,Thrust 会默认使用主机路径。若指针指向设备内存,则需要用 thrust::device_ptr 包装后再调用函数。

size_t N = 10;// raw pointer to device memory
int * raw_ptr;
cudaMalloc((void **) &raw_ptr, N * sizeof(int));// wrap raw pointer with a device_ptr
thrust::device_ptr<int> dev_ptr(raw_ptr);// use device_ptr in thrust algorithms
thrust::fill(dev_ptr, dev_ptr + N, (int) 0);

从 device_ptr 中提取原始指针:

size_t N = 10;// create a device_ptr
thrust::device_ptr<int> dev_ptr = thrust::device_malloc<int>(N);// extract raw pointer from device_ptr
int * raw_ptr = thrust::raw_pointer_cast(dev_ptr);

STL 和 互相转换:

#include <thrust/device_vector.h>
#include <thrust/copy.h>
#include <list>
#include <vector>int main(void)
{// create an STL list with 4 valuesstd::list<int> stl_list;stl_list.push_back(10);stl_list.push_back(20);stl_list.push_back(30);stl_list.push_back(40);// initialize a device_vector with the listthrust::device_vector<int> D(stl_list.begin(), stl_list.end());// copy a device_vector into an STL vectorstd::vector<int> stl_vector(D.size());thrust::copy(D.begin(), D.end(), stl_vector.begin());return 0;
}

3 Algorithms

Thrust 提供众多常见并行算法,其中许多与 STL 中的算法相对应,并采用相同名称。这些算法都有主机和设备的实现,根据使用的迭代器类型自动选择执行路径。除了 thrust::copy 外,Thrust 算法的迭代器参数应全部位于同一位置(主机或设备),否则编译器会报错。

3-1 Transformations

以下源代码演示了几种转换算法。

#include <thrust/device_vector.h>
#include <thrust/transform.h>
#include <thrust/sequence.h>
#include <thrust/copy.h>
#include <thrust/fill.h>
#include <thrust/replace.h>
#include <thrust/functional.h>
#include <iostream>int main(void)
{// allocate three device_vectors with 10 elementsthrust::device_vector<int> X(10);thrust::device_vector<int> Y(10);thrust::device_vector<int> Z(10);// initialize X to 0,1,2,3, ....thrust::sequence(X.begin(), X.end());// compute Y = -Xthrust::transform(X.begin(), X.end(), Y.begin(), thrust::negate<int>());// fill Z with twosthrust::fill(Z.begin(), Z.end(), 2);// compute Y = X mod 2thrust::transform(X.begin(), X.end(), Z.begin(), Y.begin(), thrust::modulus<int>());// replace all the ones in Y with tensthrust::replace(Y.begin(), Y.end(), 1, 10);// print Ythrust::copy(Y.begin(), Y.end(), std::ostream_iterator<int>(std::cout, "\n"));return 0;
}

3-2 Reductions

归约算法使用二元操作将输入序列减少到单个值。
例如,通过使用加法操作对数字数组进行归约,可以得到数组的和。使用 thrust::reduce 实现数组的和如下:

int sum = thrust::reduce(D.begin(), D.end(), (int) 0, thrust::plus<int>());

通过使用接受两个输入并返回最大值的操作符进行归约,可以得到数组的最大值。

 int max_value = thrust::reduce(data.begin(), data.end(),  data[0], // 初始值  thrust::maximum<int>()); // 自定义操作符  

thrust::count 返回给定序列中特定值的实例数量。

int result = thrust::count(vec.begin(), vec.end(), 1);

3-3 Prefix-Sums

并行前缀和,或扫描操作,是许多并行算法(如流压缩和基数排序)中的重要组成部分。
以下代码演示了使用默认加法运算符的 inclusive_scan 操作:

#include <thrust/scan.h>int data[6] = {1, 0, 2, 2, 1, 3};thrust::inclusive_scan(data, data + 6, data); // in-place scan// data is now {1, 1, 3, 5, 6, 9}

在包含扫描中,输出序列的每个元素都是输入范围内相应部分的和。例如,data[2] = data[0] + data[1] + data[2]。

exclusive scan 类似,但向右移动了一个位置:

#include <thrust/scan.h>int data[6] = {1, 0, 2, 2, 1, 3};thrust::exclusive_scan(data, data + 6, data); // in-place scan// data is now {0, 1, 1, 3, 5, 6}

因此,现在 data[2] = data[0] + data[1]。

Thrust 还提供了 transform_inclusive_scan 和 transform_exclusive_scan 函数,它们在执行扫描之前对输入序列应用一元函数。

3-4 Reordering

Thrust 通过以下算法提供了对分区和流压缩的支持:

  • copy_if:复制满足特定断言条件的元素到一个新的容器
#include <thrust/copy_if.h>  
#include <thrust/device_vector.h>  
#include <thrust/functional.h> // for thrust::is_even  
#include <iostream>  int main(void)  
{  // 初始化设备向量  thrust::device_vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};  // 创建一个空的设备向量用于存储结果  thrust::device_vector<int> even_numbers(data.size());  // 使用 copy_if 复制偶数  // 注意:copy_if 的结果迭代器是输出范围的开始迭代器  thrust::copy_if(data.begin(), data.end(),  even_numbers.begin(),  thrust::is_even<int>()); // 断言条件:检查是否为偶数  // 获取实际复制的元素数量  size_t even_count = thrust::distance(even_numbers.begin(),  thrust::remove_if(even_numbers.begin(), even_numbers.end(), thrust::not1(thrust::is_even<int>())).end());  // 调整 even_numbers 的大小以匹配实际元素数量  even_numbers.resize(even_count);  // 输出结果  std::cout << "Even numbers: ";  thrust::copy(even_numbers.begin(), even_numbers.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;  return 0;  
}
  • partition:partition 函数会根据提供的断言条件重新排列输入序列中的元素,使得满足条件的元素出现在不满足条件的元素之前。
#include <thrust/partition.h>  
#include <thrust/device_vector.h>  
#include <thrust/functional.h> // for thrust::less  
#include <iostream>  int main(void)  
{  // 初始化设备向量  thrust::device_vector<int> data = {1, 3, 2, 5, 4, 6, 7, 8, 9, 10};  // 使用 partition 重新排列数据,使得小于 5 的数在前  thrust::partition(data.begin(), data.end(), thrust::less<int>(5));  // 输出结果  std::cout << "Partitioned data: ";  thrust::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;  return 0;  
}
  • remove 和 remove_if:remove函数会移除给定序列中等于某个特定值的所有元素,而remove_if函数则会根据提供的断言条件移除元素。
#include <thrust/remove.h>  
#include <thrust/device_vector.h>  
#include <thrust/copy.h>  
#include <iostream>  int main(void)  
{  // 初始化设备向量  thrust::device_vector<int> data = {1, 2, 3, 2, 4, 2, 5, 6};  // 使用 remove 移除所有值为 2 的元素  thrust::device_vector<int>::iterator new_end = thrust::remove(data.begin(), data.end(), 2);  // 使用 remove_if 移除所有大于 5 的元素  // thrust::device_vector<int>::iterator new_end = thrust::remove_if(data.begin(), data.end(), thrust::greater<int>(5));// 调整数据向量的大小以匹配新的末尾迭代器  data.resize(thrust::distance(data.begin(), new_end));  // 输出结果  std::cout << "Data after removing 2s: ";  thrust::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;  return 0;  
}
  • unique:unique函数会移除序列中连续重复的元素,使得每个元素只出现一次。
#include <thrust/unique.h>  
#include <thrust/device_vector.h>  
#include <thrust/copy.h>  
#include <iostream>  int main(void)  
{  // 初始化设备向量  thrust::device_vector<int> data = {1, 2, 2, 3, 4, 4, 4, 5, 6, 6};  // 使用 unique 移除连续重复的元素  thrust::device_vector<int>::iterator new_end = thrust::unique(data.begin(), data.end());  // 调整数据向量的大小以匹配新的末尾迭代器  data.resize(thrust::distance(data.begin(), new_end));  // 输出结果  std::cout << "Data after removing consecutive duplicates: ";  thrust::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;  return 0;  
}

3-5 Sorting

Thrust 提供多种函数,可按给定条件排序或重新排列数据。其中,thrust::sort 和 thrust::stable_sort 与 STL 中的对应函数相似。

#include <thrust/sort.h>...
const int N = 6;
int A[N] = {1, 4, 2, 8, 5, 7};thrust::sort(A, A + N);// A is now {1, 2, 4, 5, 7, 8}

此外,Thrust 还有 thrust::sort_by_key 和 thrust::stable_sort_by_key,用于排序分别存储的键值对。

#include <thrust/sort.h>...
const int N = 6;
int    keys[N] = {  1,   4,   2,   8,   5,   7};
char values[N] = {'a', 'b', 'c', 'd', 'e', 'f'};thrust::sort_by_key(keys, keys + N, values);// keys is now   {  1,   2,   4,   5,   7,   8}
// values is now {'a', 'c', 'b', 'e', 'f', 'd'}

这些排序函数也支持用户自定义比较操作符。

#include <thrust/sort.h>
#include <thrust/functional.h>...
const int N = 6;
int A[N] = {1, 4, 2, 8, 5, 7};thrust::stable_sort(A, A + N, thrust::greater<int>());// A is now {8, 7, 5, 4, 2, 1}

4 Fancy Iterators

4-1 constant_iterator

constant_iterator 是一种简单的迭代器,它在每次访问时返回相同的值。

#include <thrust/iterator/constant_iterator.h>
...
// create iterators
thrust::constant_iterator<int> first(10);
thrust::constant_iterator<int> last = first + 3;first[0]   // returns 10
first[1]   // returns 10
first[100] // returns 10// sum of [first, last)
thrust::reduce(first, last);   // returns 30 (i.e. 3 * 10)

4-2 counting_iterator

counting_iterator 生成递增的序列。

#include <thrust/iterator/counting_iterator.h>
...
// create iterators
thrust::counting_iterator<int> first(10);
thrust::counting_iterator<int> last = first + 3;first[0]   // returns 10
first[1]   // returns 11
first[100] // returns 110// sum of [first, last)
thrust::reduce(first, last);   // returns 33 (i.e. 10 + 11 + 12)

4-3 transform_iterator

transform_iterator 允许我们对序列中的每个元素应用转换。

#include <thrust/iterator/transform_iterator.h>
// initialize vector
thrust::device_vector<int> vec(3);
vec[0] = 10; vec[1] = 20; vec[2] = 30;// create iterator (type omitted)
...
first = thrust::make_transform_iterator(vec.begin(), negate<int>());
...
last  = thrust::make_transform_iterator(vec.end(),   negate<int>());first[0]   // returns -10
first[1]   // returns -20
first[2]   // returns -30// sum of [first, last)
thrust::reduce(first, last);   // returns -60 (i.e. -10 + -20 + -30)// 或者
// sum of [first, last)
thrust::reduce(thrust::make_transform_iterator(vec.begin(), negate<int>()),thrust::make_transform_iterator(vec.end(),   negate<int>()));

4-4 permutation_iterator

permutation_iterator 允许我们重新排列序列中元素的顺序。通过指定一个映射,我们可以按特定顺序访问元素:

#include <thrust/iterator/permutation_iterator.h>...// gather locations
thrust::device_vector<int> map(4);
map[0] = 3;
map[1] = 1;
map[2] = 0;
map[3] = 5;// array to gather from
thrust::device_vector<int> source(6);
source[0] = 10;
source[1] = 20;
source[2] = 30;
source[3] = 40;
source[4] = 50;
source[5] = 60;// fuse gather with reduction:
//   sum = source[map[0]] + source[map[1]] + ...
int sum = thrust::reduce(thrust::make_permutation_iterator(source.begin(), map.begin()),thrust::make_permutation_iterator(source.begin(), map.end()));

4-5 zip_iterator

zip_iterator 可以将多个序列合并为一个元组序列。这使得我们可以同时处理多个序列:

#include <thrust/iterator/zip_iterator.h>
...
// initialize vectors
thrust::device_vector<int>  A(3);
thrust::device_vector<char> B(3);
A[0] = 10;  A[1] = 20;  A[2] = 30;
B[0] = 'x'; B[1] = 'y'; B[2] = 'z';// create iterator (type omitted)
first = thrust::make_zip_iterator(thrust::make_tuple(A.begin(), B.begin()));
last  = thrust::make_zip_iterator(thrust::make_tuple(A.end(),   B.end()));first[0]   // returns tuple(10, 'x')
first[1]   // returns tuple(20, 'y')
first[2]   // returns tuple(30, 'z')// maximum of [first, last)
thrust::maximum< tuple<int,char> > binary_op;
thrust::tuple<int,char> init = first[0];
thrust::reduce(first, last, init, binary_op); // returns tuple(30, 'z')

参考资料
1 Thrust docs
2 Thrust: The C++ Parallel Algorithms Library

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

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

相关文章

vue基础教程(4)——十分钟吃透vue路由router

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、路由概念二、路由使用三、创建路由对应的组件四、给整个项目一个入口总结 前言 前面的文章运行成功后&#xff0c;页面显示如下&#xff1a; 在这个页面中&#xff0c;点击Home和About都会切换右面的页面内容&#…

《责任链模式(极简c++)》

本文章属于专栏- 概述 - 《设计模式&#xff08;极简c版&#xff09;》-CSDN博客 模式说明 方案&#xff1a; 责任链模式将请求的发送者和接收者解耦&#xff0c;构成一个链条&#xff0c;并由多个对象对请求进行处理&#xff0c;直到找到合适的处理者为止。优点&#xff1a; …

iOS UIFont-真香警告之字体管理类

UIFont 系列传送门 第一弹加载本地字体:iOS UIFont-新增第三方字体 第二弹加载线上字体:iOS UIFont-实现三方字体的下载和使用 第三弹搭建字体管理类:iOS UIFont-真香警告之字体管理类 前言 不知道友们是否有过这种经历,项目已经迭代了很多版本,项目中的文件已经上千个了…

uniapp数组合并函数使用几录

let that { listAll: [1, 2, 3] }; let data [4, 5, 6]; let mergedArray that.listAll.concat(data); console.log(mergedArray); // 输出: [1, 2, 3, 4, 5, 6] console.log(that.listAll); // 输出: [1, 2, 3]&#xff0c;原始数组没有改变 唯有美景&#xff0c;可以抚…

基于SSM+Jsp+Mysql的医院远程诊断系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

故障诊断 | 基于FTNN网络模型的故障诊断(Pytorch)

效果分析 基本介绍 FTNN是一种基于神经网络的故障诊断模型,它旨在识别和定位系统中的故障。使用已标记的数据集对FTNN模型进行训练。标记的数据集包括系统在正常和故障状态下的数据,以及对应的故障标签。通过算法和优化方法,调整网络参数以最小化预测误差。使用独立的测试数…

linux三剑客之grep

grep命令 基本语法 示例 搜索文件 example.txt 中包含单词 "example" 的所有行: grep -v "example" example.txt 计算文件 example.txt 中包含 "example" 的行数: grep -c "example" example.txt 显示 example.txt 中包含 "e…

SQLite中的隔离(八)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite版本3中的文件锁定和并发(七&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 数据库的“isolation”属性确定何时对 一个操作的数据库对其他并发操作可见。 数据库连接之间的隔离 如果使用两个不…

Flume面试题及参考答案

在大数据领域,Flume是一个不可或缺的工具,它负责可靠地收集、聚合和移动大量日志数据。作为一名大数据架构师,掌握Flume的工作原理和最佳实践对于构建高效的数据处理流水线至关重要。本文将深入探讨一系列Flume面试题,并提供详尽的参考答案,以帮助读者在面试中表现出色,并…

LeetCode-2908. 元素和最小的山形三元组 I【数组,前后缀分解】

LeetCode-2908. 元素和最小的山形三元组 I【数组】 题目描述&#xff1a;解题思路一&#xff1a;暴力解法&#xff0c;三个for循环解题思路二&#xff1a;优化&#xff0c;这里注意到1 < nums[i] < 50&#xff0c;其实如果有山形三元组&#xff0c;那么result是一定小于等…

汽车电子行业知识:汽车电子领域包含哪些技术

汽车电子行业涉及到许多方面的知识&#xff0c;包括但不限于&#xff1a; 汽车电子控制单元&#xff08;ECU&#xff09;&#xff1a;负责监控和控制车辆的各种系统&#xff0c;如发动机控制单元、制动系统控制单元、空调系统控制单元等。汽车传感器技术&#xff1a;包括温度传…

Luigi任务调度框架学习2:运行每一个Task,避免因判定完成导致跳过执行Task主程序

在上一篇Luigi的线性调度文章中&#xff08;Luigi任务调度框架学习1&#xff1a;线性调用流程&#xff09;&#xff0c;我们知道Task运行的时候&#xff1a; 每个任务是否完成有两次判定&#xff0c;即&#xff1a;进行判定(未完成) 》运行def run(self)函数 》进行判定(完成) …

国产暴雨AI服务器X3418开启多元自主可控新篇章

在当前数字化转型的大潮中&#xff0c;算力作为新质生产力的重要动力引擎&#xff0c;对推动经济社会发展起着关键作用。尤其在人工智能领域&#xff0c;随着高性能、安全可控的AI算力需求持续攀升&#xff0c;国产化服务器的研发与应用显得尤为迫切。 作为国内专业的算力基础…

mysql5.7 源码分析--初始化

集中在sql\mysqld.cc文件的mysqld_main函数中&#xff08;&#xff09;&#xff1a; 主程序入口 在sql\main.cc文件中&#xff1a; int main(int argc, char **argv) {return mysqld_main(arg, argv); } 一、mysql为了跨平台&#xff0c;对win32系统做了单独的初始化&#x…

【ENSP】交换机和路由器之间实现静态路由

1.概念 三层交换机只能在Vlanif逻辑口配置iP地址 路由器的每一个端口都是另外一个网段 2.实现方法 交换机允许对应vlan通行&#xff0c;配置vlanif的ip地址&#xff0c;做静态路由 路由器&#xff0c;进接口配置对应的ip&#xff0c;路由器和交换机相连的口&#xff0c;进入他的…

文心一言 VS 讯飞星火 VS chatgpt (225)-- 算法导论16.3 7题

七、推广赫夫曼算法&#xff0c;使之能生成三进制的码字(即码字由符号0、1、2组成)&#xff0c;并证明你的算法能生成最优三进制码。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 推广赫夫曼算法以生成三进制码字需要对算法进行一定的修改&#xff0c;确保在每…

JavaScript是如何实现页面渲染的

JavaScript实现页面渲染主要涉及到对DOM的操作、样式的修改以及与后端数据的交互。以下是JavaScript实现页面渲染的主要步骤和方式&#xff1a; 一、DOM操作 创建和修改元素&#xff1a;JavaScript可以使用document.createElement()来创建新的DOM元素&#xff0c;使用appendC…

力软框架打开新的对话框,点击对话框确认按钮的事件AcceptClick的方法

// 原来在力软框架下&#xff0c;点击哪个确认按钮的时候 top.frames[iframeId].AcceptClick直接用这个方法就可以了 &#xff0c;那个方法是直接返回方法的但是不知道是什么情况。如图二所示。死活就返回了ifram标签不知道是什么原因&#xff0c;就获取不到对话框里边自己定义…

求交错数列前n项和:输⼊⼀个正整数n,计算交错序列1-2/3+3/5-4/7+5/9-6/11+... 的前n项之和。试编写相应程序。

#include <stdio.h> #include <string.h> int main() { // 求交错数列前n项和&#xff1a;输个正整数n&#xff0c;计算交错序列1-2/33/5-4/75/9-6/11... 的前n // 项之和。试编写相应程序。 int sign 1; float sum 0; int n; scanf("…

nginx界面管理工具之nginxWebUI 搭建与使用

nginx界面管理工具之nginxWebUI 搭建与使用 一、nginxWebUI 1.nginx网页配置工具 官网地址: http://www.nginxwebui.cn 源码地址&#xff1a;https://git.chihiro.org.cn/chihiro/nginxWebUI 2.功能说明 本项目可以使用WebUI配置nginx的各项功能, 包括http协议转发, tcp协议…