Numba 的 CUDA 示例(3/4):流和事件

本教程为 Numba CUDA 示例 第 3 部分。

按照本系列的第 3 部分,了解 Python CUDA 编程中的流和事件

介绍

在本系列的前两部分(第 1 部分,第 2 部分)中,我们学习了如何使用 GPU 编程执行简单的任务,例如高度并行的任务、使用共享内存的缩减以及设备功能。我们还学习了如何从主机对函数进行计时 — 以及为什么这可能不是对代码进行计时的最佳方式。

使用“墨西哥湾流多彩空间平静”运行

在本教程中

为了提高我们的计时能力,我们将介绍 CUDA 事件及其使用方法。但在深入研究之前,我们将讨论 CUDA 流及其重要性。

Google colab 中的代码:https://colab.research.google.com/drive/1jujdw9f6rf0GoOGRHCvi82mAoXD3ufmt?usp=sharing

入门

导入并加载库,确保你有 GPU。

import warnings
from time import perf_counter, sleep
import numpy as np
import numba
from numba import cuda
from numba.core.errors import NumbaPerformanceWarningprint(np.__version__)
print(numba.__version__)# 忽略 NumbaPerformanceWarning
warnings.simplefilter("ignore", category=NumbaPerformanceWarning)---
1.25.2
0.59.1
cuda.detect()---
Found 1 CUDA devices
id 0             b'Tesla T4'                              [SUPPORTED]Compute Capability: 7.5PCI Device ID: 4PCI Bus ID: 0UUID: GPU-34d689f4-4c3c-eeb0-ecce-bbaabec33618Watchdog: DisabledFP32/FP64 Performance Ratio: 32
Summary:1/1 devices are supported
True

流(Streams)

当我们从主机启动内核时,它的执行会在 GPU 中排队,只要 GPU 完成了之前启动的所有任务就会执行。

用户在设备中启动的许多任务可能依赖于先前的任务,因此“将它们放在同一个队列中”是有意义的。例如,如果你将数据异步复制到 GPU 以使用某个内核进行处理,则该副本必须在内核运行之前完成。

但是,如果你有两个彼此独立的内核,将它们放在同一个队列中是否有意义?可能没有!对于这些情况,CUDA 有。你可以将流视为彼此独立运行的单独队列。它们也可以并发运行,即同时运行。这可以在运行许多独立任务时大大加快总运行时间。

图 3.1。使用不同的流可以实现并发执行,从而缩短运行时间。

来源:Zhang et al. 2021 (CC BY 4.0).

Numba CUDA 中的流语义

我们将采取迄今为止学到的两个任务并将它们排队以创建规范化管道。给定一个(主机)数组a,我们将用其规范化版本覆盖它:

a ← a / ∑a[i]

为此,我们将使用三个内核。第一个内核 partial_reduce 是第二部分中的部分还原。它将返回一个threads_per_block-sized 数组,我们将把它传递给另一个内核 single_thread_sum,后者将进一步将其还原为一个单子数组(大小为 1)。这个内核将在单个区块和单个线程上运行。最后,我们将使用 divide_by 对原始数组和之前计算出的总和进行就地分割。所有这些操作都将在 GPU 中进行,并且应该一个接一个地运行。

threads_per_block = 256
blocks_per_grid = 32 * 40@cuda.jit
def partial_reduce(array, partial_reduction):i_start = cuda.grid(1)threads_per_grid = cuda.blockDim.x * cuda.gridDim.xs_thread = 0.0for i_arr in range(i_start, array.size, threads_per_grid):s_thread += array[i_arr]s_block = cuda.shared.array((threads_per_block,), numba.float32)tid = cuda.threadIdx.xs_block[tid] = s_threadcuda.syncthreads()i = cuda.blockDim.x // 2while (i > 0):if (tid < i):s_block[tid] += s_block[tid + i]cuda.syncthreads()i //= 2if tid == 0:partial_reduction[cuda.blockIdx.x] = s_block[0]@cuda.jit
def single_thread_sum(partial_reduction, sum):sum[0] = 0.0for element in partial_reduction:sum[0] += element@cuda.jit
def divide_by(array, val_array):i_start = cuda.grid(1)threads_per_grid = cuda.gridsize(1)for i in range(i_start, array.size, threads_per_grid):array[i] /= val_array[0]

当内核调用和其他操作没有指定流时,它们将在默认流中运行。默认流是一种特殊流,其行为取决于运行的是旧式流还是每个线程的流。对我们来说,只要说如果你想实现并发,就应该在非默认流中运行任务就足够了。让我们看看如何对某些操作(例如内核启动、数组复制和数组创建复制)做到这一点。

# Define host array
a = np.ones(10_000_000, dtype=np.float32)
print(f"Old sum: {a.sum():.2f}")
# Old sum: 10000000.00# Example 3.1: Numba CUDA Stream Semantics# Pin memory
with cuda.pinned(a):# Create a CUDA streamstream = cuda.stream()# 将数组复制到设备并在设备中创建。使用 Numba 时,可将数据流作为附加信息传递给 API 函数。dev_a = cuda.to_device(a, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)# 在启动内核时,流会被传给内核启动器("dispatcher")配置,它位于块维度(`threads_per_block`)之后。partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)# 数组复制到主机:与复制到设备一样,当传递数据流时,复制是异步的。注意:由于写入尚未同步,打印输出很可能是无意义的。dev_a.copy_to_host(a, stream=stream)# 无论何时,只要我们想确保从主机的角度来看,流中的所有操作都已完成,我们就会调用:
stream.synchronize()# 调用后,我们可以确定 `a` 已被其规范化版本覆盖
print(f"New sum: {a.sum():.2f}")---
New sum: 1.00

在我们真正谈论流之前,我们需要谈论房间里的大象:cuda.pinned。此上下文管理器创建一种称为页面锁定固定内存的特殊类型的内存,CUDA 在将内存从主机传输到设备时将受益于这种内存。

主机 RAM 中的内存可随时分页,也就是说,操作系统可以秘密地将对象从 RAM 移动到硬盘。这样做的目的是将不经常使用的对象移动到较慢的内存位置,让较快的 RAM 内存可用于更急需的对象。对我们来说重要的是,CUDA 不允许从可分页对象到 GPU 的异步传输。这样做是为了防止出现持续的非常慢的传输流:磁盘(分页)→ RAM → GPU。

要异步传输数据,我们必须确保数据始终位于 RAM 中,方法是以某种方式防止操作系统偷偷将数据隐藏在磁盘的某个地方。这就是内存固定发挥作用的地方,它创建了一个上下文,在该上下文中,参数将被“页面锁定”,即强制位于 RAM 中。参见图 3.2。

图 3.2。可分页内存与固定(页面锁定)内存

来源:Rizvi et al. 2017 (CC BY 4.0).

从此以后,代码就变得非常简单了。创建一个流,然后将其传递给我们想要在该流上操作的每个 CUDA 函数。重要的是,Numba CUDA 内核配置(方括号)要求流位于块维度大小之后的第三个参数中。

⚠️ 注意:

通常,将流传递给 Numba CUDA API 函数不会改变其行为,只会改变其运行的流。从设备到主机的复制是一个例外。调用 device_array.copy_to_host()(不带参数)时,复制会同步进行。调用 device_array.copy_to_host(stream=stream)(带流)时,如果device_array未固定,则复制将同步进行。只有在device_array固定并传递流时,复制才会异步进行。

信息:

Numba 提供了一个有用的上下文管理器,用于在其上下文中排队所有操作;退出上下文时,操作将同步,包括内存传输。示例 3.1 也可以写成:

with cuda.pinned(a):stream = cuda.stream()with stream.auto_synchronize():dev_a = cuda.to_device(a, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(a, stream=stream)

将独立内核与流分离

假设我们要标准化的不是一个数组,而是多个数组。各个数组的标准化操作完全相互独立。因此,GPU 没有必要等到一个标准化结束之后再开始下一个标准化。因此,我们应该将这些任务分成单独的流。

让我们看一个规范化 10 个数组的示例 —— 每个数组都使用自己的流。

# Example 3.2: Multiple streamsN_streams = 10
# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 10 streamsstreams = [cuda.stream() for _ in range(1, N_streams + 1)]# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]for i, arr in enumerate(arrays):print(f"Old sum (array {i}): {arr.sum():12.2f}")tics = []  # Launch start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):tic = perf_counter()with cuda.pinned(arr):dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)toc = perf_counter()  # Stop time of launchesprint(f"Launched processing {i} in {1e3 * (toc - tic):.2f} ms")# 确保删除 GPU 数组的引用,这将确保在退出上下文时进行垃圾回收。del dev_a, dev_a_reduce, dev_a_sumtics.append(tic)tocs = []for i, (stream, arr) in enumerate(zip(streams, arrays)):stream.synchronize()toc = perf_counter()  # Stop time of synctocs.append(toc)print(f"New sum (array {i}): {arr.sum():12.2f}")for i in range(4):print(f"Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms")print(f"Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms")---
Old sum (array 0):  10000000.00
Old sum (array 1):  20000000.00
Old sum (array 2):  30000000.00
Old sum (array 3):  40000000.00
Old sum (array 4):  50000000.00
Old sum (array 5):  60000000.00
Old sum (array 6):  70000000.00
Old sum (array 7):  80000000.00
Old sum (array 8):  90000000.00
Old sum (array 9): 100000000.00
Launched processing 0 in 14.40 ms
Launched processing 1 in 13.89 ms
Launched processing 2 in 13.79 ms
Launched processing 3 in 13.75 ms
Launched processing 4 in 13.62 ms
Launched processing 5 in 13.95 ms
Launched processing 6 in 13.99 ms
Launched processing 7 in 14.32 ms
Launched processing 8 in 13.14 ms
Launched processing 9 in 13.47 ms
New sum (array 0):         1.00
New sum (array 1):         1.00
New sum (array 2):         1.00
New sum (array 3):         1.00
New sum (array 4):         1.00
New sum (array 5):         1.00
New sum (array 6):         1.00
New sum (array 7):         1.00
New sum (array 8):         1.00
New sum (array 9):         1.00
Performed processing 0 in 145.23 ms
Performed processing 1 in 137.10 ms
Performed processing 2 in 129.31 ms
Performed processing 3 in 121.48 ms
Total time 207.54 ms

现在让我们与单个流进行比较。

# Example 3.3: Single stream# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 1 streamsstreams = [cuda.stream()] * N_streams# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]for i, arr in enumerate(arrays):print(f"Old sum (array {i}): {arr.sum():12.2f}")tics = []  # Launch start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):tic = perf_counter()with cuda.pinned(arr):dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)toc = perf_counter()  # Stop time of launchesprint(f"Launched processing {i} in {1e3 * (toc - tic):.2f} ms")# 确保删除 GPU 数组的引用,这将确保在退出上下文时进行垃圾回收。del dev_a, dev_a_reduce, dev_a_sumtics.append(tic)tocs = []for i, (stream, arr) in enumerate(zip(streams, arrays)):stream.synchronize()toc = perf_counter()  # Stop time of synctocs.append(toc)print(f"New sum (array {i}): {arr.sum():12.2f}")for i in range(4):print(f"Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms")print(f"Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms")---
Old sum (array 0):  10000000.00
Old sum (array 1):  20000000.00
Old sum (array 2):  30000000.00
Old sum (array 3):  40000000.00
Old sum (array 4):  50000000.00
Old sum (array 5):  60000000.00
Old sum (array 6):  70000000.00
Old sum (array 7):  80000000.00
Old sum (array 8):  90000000.00
Old sum (array 9): 100000000.00
Launched processing 0 in 13.26 ms
Launched processing 1 in 11.84 ms
Launched processing 2 in 11.83 ms
Launched processing 3 in 12.08 ms
Launched processing 4 in 14.21 ms
Launched processing 5 in 11.98 ms
Launched processing 6 in 11.91 ms
Launched processing 7 in 12.08 ms
Launched processing 8 in 12.13 ms
Launched processing 9 in 11.80 ms
New sum (array 0):         1.00
New sum (array 1):         1.00
New sum (array 2):         1.00
New sum (array 3):         1.00
New sum (array 4):         1.00
New sum (array 5):         1.00
New sum (array 6):         1.00
New sum (array 7):         1.00
New sum (array 8):         1.00
New sum (array 9):         1.00
Performed processing 0 in 124.64 ms
Performed processing 1 in 115.35 ms
Performed processing 2 in 107.26 ms
Performed processing 3 in 99.11 ms
Total time 159.02 ms

但是哪一个更快呢?运行这些示例时,使用多个流时,总时间并没有得到一致的改善。造成这种情况的原因有很多。例如,要使流并发运行,本地内存中必须有足够的空间。此外,我们从 CPU 进行计时。虽然很难知道本地内存中是否有足够的空间,但从 GPU 进行计时相对容易。让我们学习如何操作!

信息:

Nvidia 提供了多种用于调试 CUDA 的工具,包括用于调试 CUDA 流的工具。查看*Nsight Systems*了解更多信息。

事件

CPU 计时代码的一个问题是,它将包含除 GPU 之外的更多操作。

值得庆幸的是,通过 CUDA 事件可以直接从 GPU 获取时间。事件只是一个时间寄存器,它记录了 GPU 中发生的事情。在某种程度上,它类似于 time.timetime.perf_counter,但与之不同的是,我们需要处理这样一个事实:当我们在 CPU 上编程时,我们希望对来自 GPU 的事件进行计时。

因此,除了创建时间戳(“记录”事件)之外,我们还需要确保事件与 CPU 同步,然后才能访问其值。让我们看一个简单的例子。

内核执行计时事件

# Example 3.4: Simple events# 事件需要初始化,但这并不影响计时。
# 我们创建两个事件,一个在计算开始时,另一个在计算结束时。
event_beg = cuda.event()
event_end = cuda.event()# Create CUDA stream
stream = cuda.stream()with cuda.pinned(arr):# 在`stream`中复制/创建队列数组dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)# 从这一行开始,`event_beg` 将包含 GPU 中这一时刻的时间。event_beg.record(stream=stream)# 异步启动内核partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)# 启动 "记录",内核运行结束时触发该记录event_end.record(stream=stream)# 未来提交到流的任务将等待 `event_end` 完成。event_end.wait(stream=stream)# 将此事件与 CPU 同步,以便我们可以使用其值。event_end.synchronize()# 现在我们来计算执行内核所需的时间。请注意,我们不需要等待/同步`event_beg`,因为它的执行取决于 event_end 是否等待/同步了`event_beg`。
timing_ms = event_beg.elapsed_time(event_end)  # in milisecondsprint(f"Elapsed time {timing_ms:.2f} ms")---
Elapsed time 0.79 ms

对 GPU 操作进行计时的一个有用方法是使用上下文管理器:

# Example 3.5: Context Manager for CUDA Timer using Events
class CUDATimer:def __init__(self, stream):self.stream = streamself.event = None  # in msdef __enter__(self):self.event_beg = cuda.event()self.event_end = cuda.event()self.event_beg.record(stream=self.stream)return selfdef __exit__(self, type, value, traceback):self.event_end.record(stream=self.stream)self.event_end.wait(stream=self.stream)self.event_end.synchronize()self.elapsed = self.event_beg.elapsed_time(self.event_end)stream = cuda.stream()
dev_a = cuda.to_device(arrays[0], stream=stream)
dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)
with CUDATimer(stream) as cudatimer:partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)
print(f"Elapsed time {cudatimer.elapsed:.2f} ms")---
Elapsed time 0.65 ms

时间流事件

为了结束本系列的这一部分,我们将使用流来更好、更准确地了解我们的示例是否受益于流。

# Example 3.6: Timing a single streams with eventsN_streams = 10# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 1 streamstreams = [cuda.stream()] * N_streams# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]events_beg = []  # Launch start timesevents_end = []  # End start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):with cuda.pinned(arr):# 宣布事件并记录开始event_beg = cuda.event()event_end = cuda.event()event_beg.record(stream=stream)# 执行所有 CUDA 操作dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)# Record endevent_end.record(stream=stream)events_beg.append(event_beg)events_end.append(event_end)del dev_a, dev_a_reduce, dev_a_sumsleep(5)  # 等待所有事件结束,不影响 GPU 计时
for event_end in events_end:event_end.synchronize()# 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件:
elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end]
i_stream_last = np.argmax(elapsed_times)print(f"Last stream: {i_stream_last}")
print(f"Total time {elapsed_times[i_stream_last]:.2f} ms")---
Last stream: 9
Total time 117.90 ms

# Example 3.7: Timing multiple streams with events# 不要在此上下文中进行内存收集(去分配数组)
with cuda.defer_cleanup():# Create 10 streamsstreams = [cuda.stream() for _ in range(1, N_streams + 1)]# Create base arraysarrays = [i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1)]events_beg = []  # Launch start timesevents_end = []  # End start timesfor i, (stream, arr) in enumerate(zip(streams, arrays)):with cuda.pinned(arr):# 宣布事件并记录开始event_beg = cuda.event()event_end = cuda.event()event_beg.record(stream=stream)# 执行所有 CUDA 操作dev_a = cuda.to_device(arr, stream=stream)dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)dev_a.copy_to_host(arr, stream=stream)# Record endevent_end.record(stream=stream)events_beg.append(event_beg)events_end.append(event_end)del dev_a, dev_a_reduce, dev_a_sumsleep(5)  # 等待所有事件完成,不影响 GPU 时序
for event_end in events_end:event_end.synchronize()# 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件:
elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end]
i_stream_last = np.argmax(elapsed_times)print(f"Last stream: {i_stream_last}")
print(f"Total time {elapsed_times[i_stream_last]:.2f} ms")---
Last stream: 9
Total time 130.66 ms

结尾

CUDA 的核心在于性能。在本教程中,你学习了如何使用Events(事件)准确测量内核的执行时间,以便对代码进行分析。你还了解了Streams(流)以及如何使用它们来始终保持 GPU 忙碌,以及pinned(固定)或mapped arrays(映射数组),以及如何改善内存访问。

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

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

相关文章

Windows CMD对MySQL进行基本操作的常用命令

目录 前言1. 数据库操作2. 表操作3. 记录操作4. 备份与恢复数据库 前言 对于基本的命令行以及优化推荐阅读&#xff1a; 数据库中增删改常用语法语句&#xff08;全&#xff09;Mysql优化高级篇&#xff08;全&#xff09;命令行登录Mysql的详细讲解 启动MySQL服务&#xff1…

Python版《消消乐》,附源码

曾经风靡一时的消消乐&#xff0c;至今坐在地铁上都可以看到很多人依然在玩&#xff0c;想当年我也是大军中的一员&#xff0c;那家伙&#xff0c;吃饭都在玩&#xff0c;进入到高级的那种胜利感还是很爽的&#xff0c;连续消&#xff0c;无限消&#xff0c;哈哈&#xff0c;现…

代码随想录——二叉搜索树的最近公共祖先(Leetcode235)

题目链接 普通递归法 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode…

ChatGPT成知名度最高生成式AI产品,使用频率却不高

5月29日&#xff0c;牛津大学、路透社新闻研究所联合发布了一份生成式AI&#xff08;AIGC&#xff09;调查报告。 在今年3月28日—4月30日对美国、英国、法国、日本、丹麦和阿根廷的大约12,217人进行了调查&#xff0c;深度调研他们对生成式AI产品的应用情况。 结果显示&…

linux部署运维3——centos7下导入导出mysql数据库的sql文件以及查询数据量最大的表信息

在实际项目开发或者项目运维过程中&#xff0c;数据库的导入导出操作比较频繁&#xff0c;如果可以借助第三方工具那当然算喜事一桩&#xff1b;但是如果不允许外部访问&#xff0c;那么就只能使用数据库自带的命令&#xff0c;也是相当方便的。 一.导入sql文件 1.在linux命令…

基于单片机的船舱温度临界报警系统

摘 要 : 针对传统的船舱温度临界报警系统&#xff0c;由于温度监控不到位导致报警不及时的问题&#xff0c;提出一个基于单片机的船舱温度临界报警系统设计。该设计将单片机作为核心控制硬件&#xff0c;控制系统整体电路。同时设计数据采集模块&#xff0c;利用温度测量仪测试…

12 - 常用类

那就别跟他们比&#xff0c;先跟自己比&#xff0c;争取今天比昨天强一些&#xff0c;明天比今天强一些。 1.包装类 针对八种基本数据类型封装的相应的引用类型。 有了类的特点&#xff0c;就可以调用类中的方法。&#xff08;为什么要封装&#xff09; 基本数据类型包装类b…

[笔记] 记录docker-compose使用和Harbor的部署过程

容器技术 第三章 记录docker-compose使用和Harbor的部署过程 容器技术记录docker-compose使用和Harbor的部署过程Harborhttps方式部署&#xff1a;测试环境部署使用自签名SSL证书https方式部署&#xff1a;正式环境部署使用企业颁发的SSL证书给Docker守护进程添加Harbor的SSL证…

AI视频教程下载:给初学者的ChatGPT提示词技巧

你是否厌倦了花费数小时在可以通过强大的语言模型自动化的琐碎任务上&#xff1f;你是否准备好利用 ChatGPT——世界上最先进的语言模型——并将你的生产力提升到下一个水平&#xff1f; ChatGPT 是语言处理领域的游戏规则改变者&#xff0c;它能够理解并响应自然语言&#xf…

“Apache Kylin 实战指南:从安装到高级优化的全面教程

Apache Kylin是一个开源的分布式分析引擎,它提供了在Hadoop/Spark之上的SQL查询接口及多维分析(OLAP)能力,支持超大规模数据的亚秒级查询。以下是Kylin的入门教程,帮助您快速上手并使用这个强大的工具。 1. 安装Kylin Apache Kylin的安装是一个关键步骤,它要求您具备一…

C++ | Leetcode C++题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; class Solution { public:int minCut(string s) {int n s.size();vector<vector<int>> g(n, vector<int>(n, true));for (int i n - 1; i > 0; --i) {for (int j i 1; j < n; j) {g[i][j] (s[i] s[j]) &…

Windows下使用Airsim+QGC进行PX4硬件在环HITL(三)

Windows下使用AirsimQGC进行PX4硬件在环HITL This tutorial will guide you through the installation of Airsim and QGC on Windows, so that the hardware-in-the-loop experiment can be conducted. Hardware-in-the-Loop (HITL or HIL) is a simulation mode in which nor…

三维模型轻量化工具:手工模型、BIM、倾斜摄影等皆可用!

老子云是全球领先的数字孪生引擎技术及服务提供商&#xff0c;它专注于让一切3D模型在全网多端轻量化处理与展示&#xff0c;为行业数字化转型升级与数字孪生应用提供成套的3D可视化技术、产品与服务。 老子云是全球领先的数字孪生引擎技术及服务提供商&#xff0c;它专注于让…

C# 中文字符串转GBK字节的示例

一、编写思路 在 C# 中&#xff0c;将中文字符串转换为 GBK 编码的字节数组需要使用 Encoding 类。然而&#xff0c;Encoding 类虽然默认并不直接支持 GBK 编码&#xff0c;但是可以通过以下方式来实现这一转换&#xff1a; 1.使用系统已安装的编码提供者&#xff08;如果系统…

Unity 之 代码修改材质球贴图

Unity 之 代码修改材质球贴图 代码修改Shader&#xff1a;ShaderGraph&#xff1a;材质球包含属性 代码修改 meshRenderer.material.SetTexture("_Emission", texture);Shader&#xff1a; ShaderGraph&#xff1a; 材质球包含属性 materials[k].HasProperty("…

S4 BP 维护

前台输入Tcode:BP 问候填写金税开票信息使用的开户行名称,注释填写金税开票信息使用的开户行代码 屏幕下滑按需填写其他数据,如:街道2,街道3,街道/门牌号,街道4,街道5,区域,邮编、城市、国家、地区、语言,电话(发票地址里的电话(必须是客户开票资料里提供的电话,会…

团队项目开发使用git工作流(IDEA)【精细】

目录 开发项目总体使用git流程 图解流程 1.创建项目仓库[组长完成] 2. 创建项目&#xff0c;并进行绑定远程仓库【组长完成】 3.将项目与远程仓库&#xff08;gitee&#xff09;进行绑定 3.1 创建本地的git仓库 3.2 将项目添加到缓存区 3.3 将项目提交到本地仓库&#…

读书笔记-Java并发编程的艺术-第2章 Java并发机制的底层实现原理

文章目录 2.1 volatile的应用2.1.1 volatile的定义与实现原理2.1.2 volatile的使用优化 2.2 synchronized的实现原理与应用2.2.1 Java对象头2.2.2 锁的升级与对比2.2.2.1 偏向锁2.2.2.2 轻量级锁2.2.2.3 锁的优缺点对比 2.3 原子操作的实现原理2.3.1 术语定义2.3.2 处理器如何实…

Git常用命令1

1、设置用户签名 ①基本语法&#xff1a; git config --global user.name 用户名 git config --global user.email 邮箱 ②实际操作 ③查询是否设置成功 cat ~/.gitconfig 注&#xff1a;签名的作用是区分不同操作者身份。用户的签名信息在每一个版本的提交…

GO语言 服务发现概述

https://zhuanlan.zhihu.com/p/32027014 明明白白的聊一下什么是服务发现-CSDN博客 一、服务发现 是什么 在传统的系统部署中&#xff0c;服务运行在一个固定的已知的 IP 和端口上&#xff0c;如果一个服务需要调用另外一个服务&#xff0c;可以通过地址直接调用。 但是&…