从头开始进行CUDA编程:线程间协作的常见技术

在前一篇文章中,我们介绍了如何使用 GPU 运行的并行算法。这些并行任务是那些完全相互独立的任务,这点与我们一般认识的编程方式有很大的不同,虽然我们可以从并行中受益,但是这种奇葩的并行运行方式对于我们来说肯定感到非常的复杂。所以在本篇文章的Numba代码中,我们将介绍一些允许线程在计算中协作的常见技术。

首先还是载入相应的包

 from time import perf_counter
 
 import numpy as np
 
 import numba
 from numba import cuda
 
 print(np.__version__)
 print(numba.__version__)
 
 cuda.detect()
 
 # 1.21.6
 # 0.55.2
 
 # Found 1 CUDA devices
 # id 0             b'Tesla T4'                              [SUPPORTED]
 #                       Compute Capability: 7.5
 #                            PCI Device ID: 4
 #                               PCI Bus ID: 0
 #                                     UUID: GPU-bcc6196e-f26e-afdc-1db3-6eba6ff160f0
 #                                 Watchdog: Disabled
 #              FP32/FP64 Performance Ratio: 32
 # Summary:
 # 1/1 devices are supported
 # True

不要忘记,我们这里是CUDA编程,所以NV的GPU是必须的,比如可以去colab或者Kaggle白嫖。

线程间的协作

简单的并行归约算法

我们将从一个非常简单的问题开始本节:对数组的所有元素求和。这个算法非常简单。如果不使用NumPy,我们可以这样实现它:

 def sum_cpu(array):
     s = 0.0
     for i in range(array.size):
         s += array[i]
     return s

这看起来不是很 Pythonic。但它能够让我们了解它正在跟踪数组中的所有元素。如果 s 的结果依赖于数组的每个元素,我们如何并行化这个算法呢?首先,我们需要重写算法以允许并行化, 如果有无法并行化的部分则应该允许线程相互通信。

到目前为止,我们还没有学会如何让线程相互通信……事实上,我们之前说过不同块中的线程不通信。我们可以考虑只启动一个块,但是我们上次也说了,在大多数 GPU 中块只能有 1024 个线程!

如何克服这一点?如果将数组拆分为 1024 个块(或适当数量的threads_per_block)并分别对每个块求和呢?然后最后,我们可以将每个块的总和的结果相加。下图显示了一个非常简单的 2 块拆分示例。

上图就是对数组元素求和的“分而治之”方法。

如何在 GPU 上做到这一点呢?首先需要将数组拆分为块。每个数组块将只对应一个具有固定数量的线程的CUDA块。在每个块中,每个线程可以对多个数组元素求和。然后将这些每个线程的值求和,这里就需要线程进行通信,我们将在下一个示例中讨论如何通信。

由于我们正在对块进行并行化,因此内核的输出应该被设置为一个块。为了完成Reduce,我们将其复制到 CPU 并在那里完成工作。

 threads_per_block = 1024  # Why not!
 blocks_per_grid = 32 * 80  # Use 32 * multiple of streaming multiprocessors
 
 # Example 2.1: Naive reduction
 @cuda.jit
 def reduce_naive(array, partial_reduction):
     i_start = cuda.grid(1)
     threads_per_grid = cuda.blockDim.x * cuda.gridDim.x
     s_thread = 0.0
     for i_arr in range(i_start, array.size, threads_per_grid):
         s_thread += array[i_arr]
 
     # We need to create a special *shared* array which will be able to be read
     # from and written to by every thread in the block. Each block will have its
     # own shared array. See the warning below!
     s_block = cuda.shared.array((threads_per_block,), numba.float32)
     
     # We now store the local temporary sum of a single the thread into the
     # shared array. Since the shared array is sized
     #     threads_per_block == blockDim.x
     # (1024 in this example), we should index it with `threadIdx.x`.
     tid = cuda.threadIdx.x
     s_block[tid] = s_thread
     
     # The next line synchronizes the threads in a block. It ensures that after
     # that line, all values have been written to `s_block`.
     cuda.syncthreads()
 
     # Finally, we need to sum the values from all threads to yield a single
     # value per block. We only need one thread for this.
     if tid == 0:
         # We store the sum of the elements of the shared array in its first
         # coordinate
         for i in range(1, threads_per_block):
             s_block[0] += s_block[i]
         # Move this partial sum to the output. Only one thread is writing here.
         partial_reduction[cuda.blockIdx.x] = s_block[0]

这里需要注意的是必须共享数组,并且让每个数组块变得“小”

这里的“小”:确切大小取决于 GPU 的计算能力,通常在 48 KB 和 163 KB 之间。请参阅此表中的“每个线程块的最大共享内存量”项。(https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#features-and-technical-specifications__technical-specifications-per-compute-capability)

在编译时有一个已知的大小(这就是我们调整共享数组threads_per_block而不是blockDim.x的原因)。我们总是可以为任何大小的共享数组定义一个工厂函数……但要注意这些内核的编译时间。

这里的数组需要为 Numba 类型指定的 dtype,而不是 Numpy 类型(这个没有为什么!)。

 N = 1_000_000_000
 a = np.arange(N, dtype=np.float32)
 a /= a.sum() # a will have sum = 1 (to float32 precision)
 
 s_cpu = a.sum()
 
 # Highly-optimized NumPy CPU code
 timing_cpu = np.empty(21)
 for i in range(timing_cpu.size):
     tic = perf_counter()
     a.sum()
     toc = perf_counter()
     timing_cpu[i] = toc - tic
 timing_cpu *= 1e3  # convert to ms
 
 print(f"Elapsed time CPU: {timing_cpu.mean():.0f} ± {timing_cpu.std():.0f} ms")
 # Elapsed time CPU: 354 ± 24 ms
 
 dev_a = cuda.to_device(a)
 dev_partial_reduction = cuda.device_array((blocks_per_grid,), dtype=a.dtype)
 
 reduce_naive[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction)
 s = dev_partial_reduction.copy_to_host().sum()  # Final reduction in CPU
 
 np.isclose(s, s_cpu)  # Ensure we have the right number
 # True
 
 timing_naive = np.empty(21)
 for i in range(timing_naive.size):
     tic = perf_counter()
     reduce_naive[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction)
     s = dev_partial_reduction.copy_to_host().sum()
     cuda.synchronize()
     toc = perf_counter()
     assert np.isclose(s, s_cpu)    
     timing_naive[i] = toc - tic
 timing_naive *= 1e3  # convert to ms
 
 print(f"Elapsed time naive: {timing_naive.mean():.0f} ± {timing_naive.std():.0f} ms")
 # Elapsed time naive: 30 ± 12 ms

在谷歌Colab上测试,得到了10倍的加速。

题外话:上面这个方法之所以说是简单的规约算法,是因为这个算法最简单,也最容易实现。我们在大数据中常见的Map-Reduce算法就是这个算法。虽然实现简单,但是他容易理解,所以十分常见,当然他慢也是出名的,有兴趣的大家可以去研究研究。

一种更好的并行归约算法

上面的算法最 “朴素”的,所以有很多技巧可以加快这种代码的速度(请参阅 CUDA 演示文稿中的 Optimizing Parallel Reduction 以获得基准测试)。

在介绍更好的方法之前,让我们回顾一下内核函数的的最后一点:

 if tid == 0:  # Single thread taking care of business
     for i in range(1, threads_per_block):
         s_block[0] += s_block[i]
     partial_reduction[cuda.blockIdx.x] = s_block[0]

我们并行化了几乎所有的操作,但是在内核的最后,让一个线程负责对共享数组 s_block 的所有 threads_per_block 元素求和。为什么不能把这个总和也并行化呢?

听起来不错对吧,下图显示了如何在 threads_per_block 大小为 16 的情况下实现这一点。我们从 8 个线程开始工作,第一个将对 s_block[0] 和 s_block[8] 中的值求和。第二个求和s_block[1]和s_block[9]中的值,直到最后一个线程将s_block[7]和s_block[15]的值相加。

在下一步中,只有前 4 个线程需要工作。第一个线程将对 s_block[0] 和 s_block[4] 求和;第二个,s_block[1] 和 s_block[5];第三个,s_block[2] 和 s_block[6];第四个也是最后一个,s_block[3] 和 s_block[7]。

第三步,只需要 2 个线程来处理 s_block 的前 4 个元素。

第四步也是最后一步将使用一个线程对 2 个元素求和。

由于工作已在线程之间分配,因此它是并行化的。这不是每个线程的平等划分,但它是一种改进。在计算上,这个算法是 O(log2(threads_per_block)),而上面“朴素”算法是 O(threads_per_block)。比如在我们这个示例中是 1024 次操作,用于 了两个算法差距有10倍

最后还有一个细节。在每一步,我们都需要确保所有线程都已写入共享数组。所以我们必须调用 cuda.syncthreads()。

 # Example 2.2: Better reduction
 @cuda.jit
 def reduce_better(array, partial_reduction):
     i_start = cuda.grid(1)
     threads_per_grid = cuda.blockDim.x * cuda.gridDim.x
     s_thread = 0.0
     for i_arr in range(i_start, array.size, threads_per_grid):
         s_thread += array[i_arr]
 
     # We need to create a special *shared* array which will be able to be read
     # from and written to by every thread in the block. Each block will have its
     # own shared array. See the warning below!
     s_block = cuda.shared.array((threads_per_block,), numba.float32)
     
     # We now store the local temporary sum of the thread into the shared array.
     # Since the shared array is sized threads_per_block == blockDim.x,
     # we should index it with `threadIdx.x`.
     tid = cuda.threadIdx.x
     s_block[tid] = s_thread
     
     # The next line synchronizes the threads in a block. It ensures that after
     # that line, all values have been written to `s_block`.
     cuda.syncthreads()
 
     i = cuda.blockDim.x // 2
     while (i > 0):
         if (tid < i):
             s_block[tid] += s_block[tid + i]
         cuda.syncthreads()
         i //= 2
 
     if tid == 0:
         partial_reduction[cuda.blockIdx.x] = s_block[0]
 
 
 reduce_better[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction)
 s = dev_partial_reduction.copy_to_host().sum()  # Final reduction in CPU
 
 np.isclose(s, s_cpu)
 # True
 
 timing_naive = np.empty(21)
 for i in range(timing_naive.size):
     tic = perf_counter()
     reduce_better[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction)
     s = dev_partial_reduction.copy_to_host().sum()
     cuda.synchronize()
     toc = perf_counter()
     assert np.isclose(s, s_cpu)    
     timing_naive[i] = toc - tic
 timing_naive *= 1e3  # convert to ms
 
 print(f"Elapsed time better: {timing_naive.mean():.0f} ± {timing_naive.std():.0f} ms")
 # Elapsed time better: 22 ± 1 ms

可以看到,这比原始方法快25%。

重要说明:你可能很想将同步线程移动到 if 块内,因为在每一步之后,超过当前线程数一半的内核将不会被使用。但是这样做会使调用同步线程的 CUDA 线程停止并等待所有其他线程,而所有其他线程将继续运行。因此停止的线程将永远等待永远不会停止同步的线程。如果您同步线程,请确保在所有线程中调用 cuda.syncthreads()。

 i = cuda.blockDim.x // 2
 while (i > 0):
     if (tid < i):
         s_block[tid] += s_block[tid + i]
         #cuda.syncthreads()  这个是错的
     cuda.syncthreads()  # 这个是对的
     i //= 2

Numba 自动归约

其实归约算法并不简单,所以Numba 提供了一个方便的 cuda.reduce 装饰器,可以将二进制函数转换为归约。所以上面冗长而复杂的算法可以替换为:

 @cuda.reduce
 def reduce_numba(a, b):
     return a + b
 
 # Compile and check
 s = reduce_numba(dev_a)
 
 np.isclose(s, s_cpu)
 # True
 
 # Time
 timing_numba = np.empty(21)
 for i in range(timing_numba.size):
     tic = perf_counter()
     s = reduce_numba(dev_a)
     toc = perf_counter()
     assert np.isclose(s, s_cpu)    
     timing_numba[i] = toc - tic
 timing_numba *= 1e3  # convert to ms
 
 print(f"Elapsed time better: {timing_numba.mean():.0f} ± {timing_numba.std():.0f} ms")
 # Elapsed time better: 45 ± 0 ms

上面的运行结果我们可以看到手写代码通常要快得多(至少 2 倍),但 Numba 给我们提供的方法却非常容易使用。这对我们来说是格好事,因为终于有我们自己实现的Python方法比官方的要快了?

这里还有一点要注意就是默认情况下,要减少复制因为这会强制同步。为避免这种情况可以使用设备上数组作为输出调用归约:

 dev_s = cuda.device_array((1,), dtype=s)
 
 reduce_numba(dev_a, res=dev_s)
 
 s = dev_s.copy_to_host()[0]
 np.isclose(s, s_cpu)
 # True

二维规约示例

并行约简技术是非常伟大的,如何将其扩展到更高的维度?虽然我们总是可以使用一个展开的数组(array2 .ravel())调用,但了解如何手动约简多维数组是很重要的。

在下面这个例子中,将结合刚才所学的知识来计算二维数组。

 threads_per_block_2d = (16, 16)  #  256 threads total
 blocks_per_grid_2d = (64, 64)
 
 # Total number of threads in a 2D block (has to be an int)
 shared_array_len = int(np.prod(threads_per_block_2d))
 
 # Example 2.4: 2D reduction with 1D shared array
 @cuda.jit
 def reduce2d(array2d, partial_reduction2d):
     ix, iy = cuda.grid(2)
     threads_per_grid_x, threads_per_grid_y = cuda.gridsize(2)
 
     s_thread = 0.0
     for i0 in range(iy, array2d.shape[0], threads_per_grid_x):
         for i1 in range(ix, array2d.shape[1], threads_per_grid_y):
             s_thread += array2d[i0, i1]
 
     # Allocate shared array
     s_block = cuda.shared.array(shared_array_len, numba.float32)
 
     # Index the threads linearly: each tid identifies a unique thread in the
     # 2D grid.
     tid = cuda.threadIdx.x + cuda.blockDim.x * cuda.threadIdx.y
     s_block[tid] = s_thread
 
     cuda.syncthreads()
 
     # We can use the same smart reduction algorithm by remembering that
     #     shared_array_len == blockDim.x * cuda.blockDim.y
     # So we just need to start our indexing accordingly.
     i = (cuda.blockDim.x * cuda.blockDim.y) // 2
     while (i != 0):
         if (tid < i):
             s_block[tid] += s_block[tid + i]
         cuda.syncthreads()
         i //= 2
     
     # Store reduction in a 2D array the same size as the 2D blocks
     if tid == 0:
         partial_reduction2d[cuda.blockIdx.x, cuda.blockIdx.y] = s_block[0]
 
 
 N_2D = (20_000, 20_000)
 a_2d = np.arange(np.prod(N_2D), dtype=np.float32).reshape(N_2D)
 a_2d /= a_2d.sum() # a_2d will have sum = 1 (to float32 precision)
 
 s_2d_cpu = a_2d.sum()
 
 dev_a_2d = cuda.to_device(a_2d)
 dev_partial_reduction_2d = cuda.device_array(blocks_per_grid_2d, dtype=a.dtype)
 
 reduce2d[blocks_per_grid_2d, threads_per_block_2d](dev_a_2d, dev_partial_reduction_2d)
 s_2d = dev_partial_reduction_2d.copy_to_host().sum()  # Final reduction in CPU
 
 np.isclose(s_2d, s_2d_cpu)  # Ensure we have the right number
 # True
 
 timing_2d = np.empty(21)
 for i in range(timing_2d.size):
     tic = perf_counter()
     reduce2d[blocks_per_grid_2d, threads_per_block_2d](dev_a_2d, dev_partial_reduction_2d)
     s_2d = dev_partial_reduction_2d.copy_to_host().sum()
     cuda.synchronize()
     toc = perf_counter()
     assert np.isclose(s_2d, s_2d_cpu)    
     timing_2d[i] = toc - tic
 timing_2d *= 1e3  # convert to ms
 
 print(f"Elapsed time better: {timing_2d.mean():.0f} ± {timing_2d.std():.0f} ms")
 # Elapsed time better: 15 ± 4 ms

设备函数

到目前为止,我们只讨论了内核函数,它是启动线程的特殊GPU函数。内核通常依赖于较小的函数,这些函数在GPU中定义,只能访问GPU数组。这些被称为设备函数(Device functions)。与内核函数不同的是,它们可以返回值。

我们将展示一个跨不同内核使用设备函数的示例。该示例还将展示在使用共享数组时同步线程的重要性。

在CUDA的新版本中,内核可以启动其他内核。这被称为动态并行,但是Numba 的CUDA API还不支持。

我们将在固定大小的数组中创建波纹图案。首先需要声明将使用的线程数,因为这是共享数组所需要的。

 threads_16 = 16
 
 import math
 
 @cuda.jit(device=True, inline=True)  # inlining can speed up execution
 def amplitude(ix, iy):
     return (1 + math.sin(2 * math.pi * (ix - 64) / 256)) * (
         1 + math.sin(2 * math.pi * (iy - 64) / 256)
     )
 
 # Example 2.5a: 2D Shared Array
 @cuda.jit
 def blobs_2d(array2d):
     ix, iy = cuda.grid(2)
     tix, tiy = cuda.threadIdx.x, cuda.threadIdx.y
 
     shared = cuda.shared.array((threads_16, threads_16), numba.float32)
     shared[tiy, tix] = amplitude(iy, ix)
     cuda.syncthreads()
 
     array2d[iy, ix] = shared[15 - tiy, 15 - tix]
 
 # Example 2.5b: 2D Shared Array without synchronize
 @cuda.jit
 def blobs_2d_wrong(array2d):
     ix, iy = cuda.grid(2)
     tix, tiy = cuda.threadIdx.x, cuda.threadIdx.y
 
     shared = cuda.shared.array((threads_16, threads_16), numba.float32)
     shared[tiy, tix] = amplitude(iy, ix)
 
     # When we don't sync threads, we may have not written to shared
     # yet, or even have overwritten it by the time we write to array2d
     array2d[iy, ix] = shared[15 - tiy, 15 - tix]
 
 
 N_img = 1024
 blocks = (N_img // threads_16, N_img // threads_16)
 threads = (threads_16, threads_16)
 
 dev_image = cuda.device_array((N_img, N_img), dtype=np.float32)
 dev_image_wrong = cuda.device_array((N_img, N_img), dtype=np.float32)
 
 blobs_2d[blocks, threads](dev_image)
 blobs_2d_wrong[blocks, threads](dev_image_wrong)
 
 image = dev_image.copy_to_host()
 image_wrong = dev_image_wrong.copy_to_host()
 
 import matplotlib.pyplot as plt
 
 fig, (ax1, ax2) = plt.subplots(1, 2)
 ax1.imshow(image.T, cmap="nipy_spectral")
 ax2.imshow(image_wrong.T, cmap="nipy_spectral")
 for ax in (ax1, ax2):
     ax.set_xticks([])
     ax.set_yticks([])
     ax.set_xticklabels([])
     ax.set_yticklabels([])

左:同步(正确)内核的结果。正确:来自不同步(不正确)内核的结果。

总结

本文介绍了如何开发需要规约模式来处理1D和2D数组的内核函数。在这个过程中,我们学习了如何利用共享数组和设备函数。如果你对文本感兴趣,请看源代码:

https://colab.research.google.com/drive/1GkGLDexnYUnl2ilmwNxAlWAH6Eo5ZK2f?usp=sharing

作者:Carlos Costa, Ph.D.

不要忘记我们以前的文章

本文转载于网络 如有侵权请联系删除

相关文章

  • 消息称腾讯正式宣布成立“XR”部门,押注元宇宙;谷歌前 CEO:美国即将输掉芯片竞争,要让台积电、三星建更多工厂...

    01消息称腾讯正式宣布成立“XR”部门,押注元宇宙6月20日消息,据报道,三位知情人士称,腾讯控股周一向其员工宣布正式成立“扩展现实”(XR)部门,正式将赌注押在虚拟世界的元宇宙概念上。消息人士称,该部门的任务是为腾讯建立包括软件和硬件在内的扩展现实业务,并将成为该公司互动娱乐事业群(IEG)的一部分。(新浪科技)02俄禁止Meta公司在俄活动判决正式生效当地时间6月20日,俄罗斯莫斯科市法院宣布驳回Meta公司上诉,承认下级法院于今年3月21日有关“承认Meta公司为极端组织,禁止该公司在俄活动”的判决合法。当地时间3月21日,俄罗斯莫斯科维特尔地方法院裁定Meta为极端组织。法院同时宣布有关脸书(Facebook)和照片墙(Instagram)的禁令应立即开始执行。(央视网)03量子计算专家、ACM计算奖得主加盟OpenAI近日,量子计算专家、ACM计算奖得主ScottAaronson通过博客宣布,将于本周离开得克萨斯大学奥斯汀分校(UTAustin)一年,并加盟人工智能研究公司OpenAI。在OpenAI,Aaronson将研究人工智能安全和一致性(AISafetyandAlig

  • 端到端基于图像的伪激光雷达3D目标检测

    点击上方“3D视觉工坊”,选择“星标”干货第一时间送达标题:End-to-EndPseudo-LiDARforImage-Based3DObjectDetection作者:RuiQian,DivyanshGarg,YanWang,YurongYou,SergeBelongie,BharathHariharan,MarkCampbell,KilianQ.Weinberger,Wei-LunChao来源:CVPR2020编译:Cirstan审核:wyc摘要大家好,今天为大家带来的文章是End-to-EndPseudo-LiDARforImage-Based3DObjectDetection可靠、准确的三维物体检测是安全自主驾驶的必要条件。尽管激光雷达传感器可以提供精确的三维点云环境估计值,但在许多情况下,它们的成本也高得让人望而却步。最近,伪激光雷达(PL)的引入使得基于LiDAR传感器的方法与基于廉价立体相机的方法之间的精度差距大大缩小。PL通过将二维深度图输出转换为三维点云输入,将用于三维深度估计的最新深度神经网络与用于三维目标检测的深度神经网络相结合。然而,到目前为止,这两个网络必须

  • LeetCode - 移除元素

    LeetCode第27题,难度简单。鉴于题目太长了,所以我就只复制了一部分内容,剩下的内容各位可以自己点击下方链接查看原题的示例和说明。原题地址:https://leetcode-cn.com/problems/remove-element/题目描述:给定一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/remove-element著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。解题思路:要求不使用额外的数组空间,只能在原数组上动手脚,那么思路就来了。遍历整个数组,如果值不等于val,那么这个位置就还是它;如果值等于val,那么跳过,下一个不等于val的值就会填到这个位置,最后返回值不等于val的元素数量即可。题目说明了不需要处理数组中超出新长度后面的元素,所以就可以不对齐进行

  • Scrapy之探讨3个细节

    1.多个pipline的处理在Scrapy的框架中,其实可以有好多pipline。大家在编写pipline的时候有没有这样的一个疑问,为什么pipline函数最后要有一个语句是<spanstyle="font-size:18px;">returnitem</span>复制    明明item是传入pipline的,怎么又传出去了呢。   大家还记得在使用pipline的时候,在setting文件中要写一个数字么,   比如:XXX.XXX.Mypipline:300   这里的300就是这个pipline的运行次序。也就是说,你可以写很多pipline去处理一个item。有点像流水线加工哦。很标准的模块化思想呢!   所以你可以写一个pipline用来清洗数据,一个pipline用来存储。当然,不要忘了后面的序号不要弄错,因为那代表着顺序。2.Request参数解析   request我们之前使用的时候,一般都只用到了两个参数,一个是url一个是callball调用的回调函数。其实这个函数还有很多参数。classscrapy.http.Req

  • 最最最常见的Java面试题总结-第一周

    这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。Github地址:https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md一Java中的值传递和引用传递(非常重要)首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”那么什么是值传递和应用传递呢?值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子:值传递和应用传递实例1.值传递publ

  • 企业以太坊联盟:区块链挑战者

    5月初,印度IT服务和咨询巨头Wipro宣布已加入企业以太坊联盟(EEA),成为创始成员。该联盟围绕基于开放源码区块链的平台Ethereum而形成,于2月底正式推出,其中30个创始成员开发企业级区块链解决方案。“企业以太坊是快速采用企业应用的好方法,而Ethereum是我们客户用于开发和部署企业区块链的增长最快的技术平台之一”,Wipro服务转型副总裁KrishnakumarMenon。以太坊Ethereum于2015年7月推出,是一款基于区块链的开源通用分散式应用平台,支持智能合约功能。它采用以太坊虚拟机和Solidity编程语言直接在其他应用程序间实施和执行点对点和多方协议。“以太坊已经成为开发和部署企业区块链的最广泛使用的技术之一。企业喜欢开源实现,单一标准,快速增长的开发人员生态系统以及人才的可用性。但企业希望有弹性的安全系统和强大的控制环境。EEA的目标是将这些组合起来,为企业提供他们需要的论坛,并推动以太坊将军,“EEA的创始董事会成员杰里米米勒(JeremyMillar)。许多初始成员使用以太坊开发了演示应用和生产环境,并带来了对企业需求的独特理解。其中包括供应链原产地追

  • 项目管理中工时计算的问题

    项目管理中工时计算的问题背景为什么项目总是不能按时结项?为什么工期一再延误?员工不够努力吗?时间去了哪里?面临的问题普遍问题是,我们至今对知识型工作者的做事效率,仍采用工业时代的评价模式。若工作者每小时的效率产出基本一致,那关注他们的工作时长便行之有理。对于重复性劳动,这种评价模式可能确实管用,但对知识型工作者就不太适用。工时去了哪里?据统计一个典型的美国办公室工作者,每个工作日只能完成90分钟真正有意义的工作。当天剩余的大部分时间,都被浪费在各种分心事务上,比如阅读新闻、网上冲浪、同事社交、吃零食、喝咖啡、翻看报纸、处理无关邮件、不必要的拖延行为、玩游戏、做白日梦等。多说一句,美国办公室工作者在世界范围内还算最高效的人群。在其他很多国家,人们每天完成的实质工作甚至更少。洗手间,茶水间,吸烟洗手间,先不分男女性别差异以及大小号,平均下来10~15分钟一次没有意见吧?那么一天至少两次吧?茶水间,洗杯子,泡茶,清理茶叶残渣,遇到同时还需要寒暄几句。需要10分钟吧?一天至少2~4次吧?吸烟,我不吸烟不太清楚,但是看到公司的烟友抽的爽,了得欢。20分钟要吧?看邮件,写邮件我最讨厌邮件群发,或将

  • 腾讯云数据万象MultiJobs任务与工作流

    功能描述提交多个任务。 APIExplorer提供了在线调用、签名验证、SDK代码生成和快速检索接口等能力。您可查看每次调用的请求内容和返回结果以及自动生成SDK调用示例。 请求请求示例POST/jobsHTTP/1.1 Host:<BucketName-APPID>.ci.<Region>.myqcloud.com Date:<GMTDate> Authorization:<AuthString> Content-Length:<length> Content-Type:application/xml <body>复制 说明: Authorization:AuthString(详情请参见请求签名文档)。 通过子账号使用时,需要授予相关的权限,详情请参见授权粒度详情文档。 请求头此接口仅使用公共请求头部,详情请参见公共请求头部文档。 请求体该请求操作的实现需要有如下请求体: <Request> <Input> <Object>inpu

  • 如何保障微服务架构下的数据一致性?

    写在前面 随着微服务架构的推广,越来越多的公司采用微服务架构来构建自己的业务平台。就像前边的文章说的,微服务架构为业务开发带来了诸多好处的同时,例如单一职责、独立开发部署、功能复用和系统容错等等,也带来一些问题。 例如上手难度变大,运维变得更复杂,模块之间的依赖关系更复杂,数据一致性难以保证,等等。但是办法总是比问题多,本篇文章就来介绍一下我们是如何保障微服务架构的数据一致性的。 微服务架构的数据一致性问题 以电商平台为例,当用户下单并支付后,系统需要修改订单的状态并且增加用户积分。由于系统采用的是微服务架构,分离出了支付服务、订单服务和积分服务,每个服务都有独立数据库做数据存储。当用户支付成功后,无论是修改订单状态失败还是增加积分失败,都会造成数据的不一致。 为了解决例子中的数据一致性问题,一个最直接的办法就是考虑数据的强一致性。那么如何保证数据的强一致性呢?我们从关系型数据库的ACID理论说起。 ACID 关系型数据库具有解决复杂事务场景的能力,关系型数据库的事务满足ACID的特性。 Atomicity:原子性(要么都做,要么都不做) Consistency:一致性(数据

  • 20162313 苑洪铭 实验四 图的实现与应用

    20162313苑洪铭实验四图的实现与应用 实验1 要求 用邻接矩阵实现无向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器 给出伪代码,产品代码,测试代码(不少于5条测试) 内容 邻接矩阵(AdjacencyMatrix):是表示顶点之间相邻关系的矩阵。设G=(V,E)是一个图,其中V={v1,v2,…,vn}。 对无向图而言,邻接矩阵一定是对称的,而且主对角线一定为零,副对角线不一定为0。 总的来说,这次的代码首先定义一下顶点和边, 然后在此基础上,编写添加删除操作以及size、isempty方法 如图在size方法直接返回前文中定义的size值即可 而isempty方法我认为返回size值为0时就可以了。 深度广度迭代器课本上就有现成的代码。 并且该试验中的大部分代码都可以在老师给的PPT上找到,包括伪代码等等。 实验2 要求 用十字链表实现无向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器

  • break语句和continue语句

    如果我们想要让程序在中途跳出循环,可以用break语句来实现 #include<stdio.h> intmain(){inti,num;_Boolflag=1;printf("请输入一个整数:");scanf("%d",&num);for(i=2;i<=num/2;i++){if(num%i==0){flag=0;break;}}if(flag){printf("%d是一个素数!\n",num);}else{printf("%d不是一个素数!\n",num);}printf("i=%d\n",i);return0;} 如果写了break,输入10000,i的值为2,因为它不是素数,i=2是,由于break,直接跳出循环 没写i=5000 #include<stdio.h> intmain() { inti,j; for(i=0;i<10;i++) { for(j=0;j<10;j++) { if(j==3) { break; } } } printf("i=%d,j=%d\n",i,j); return0; }复制 结果 i=10

  • 《明朝那些事儿》读后感

    “很多人问,为什么看历史;很多人回答,以史为鉴。 现在我来告诉你,以史为鉴,是不可能的。 因为我发现,其实历史没有变化,技术变了,衣服变了,饮食变了,这都是外壳,里面什么都没变化,还是几千年前那一套,转来转去,该犯的错误还是要犯,该杀的人还是要杀,岳飞会死,袁崇焕会死,再过一千年,还是会死。 所有发生的,是因为它有发生的理由,能超越历史的人,才叫以史为鉴,然而,我们终究不能超越,因为我们自己的欲望和弱点。 所有的错误,我们都知道,然而终究改不掉。 能改的,叫做缺点,不能改的,叫做弱点。 顺便说下,能超越历史的人,还是有的,我们管这种人,叫做圣人。 ” “人生并非如某些人所说,很短暂,事实上,有时候,它很漫长,特别是对苦难中的人,漫长的想死。 但我坚持,无论有多绝望,无论有多悲哀,每天早上起来,都要对自己说,这个世界很好、很强大。 ” 印象深刻的几位。 朱元璋和朱棣的杀伐果断,即使错杀一千也不错过一个,虽说些许残暴(类比大屠杀)但斩草除根了,仁柔只能导致失败;这两位励精图治,明朝后期文官宦官当道,君主有名无实不作为。 张居正,万历皇帝的老师,无比复杂的人。深谙权谋,不择手段,清除异己,贪

  • Redis学习笔记

    Redis介绍Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库.它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:字符串类型;散列类型;列表类型;集合类型;有序集合类型 第一步:下载   https://files.cnblogs.com/files/wangjifeng23/Redis-x64-3.2.100.zip ,其余版本可前往官网进行下载 http://download.redis.io/releases/ 。 注意:如果版本有问题,自行去官网或者github下载其他Redis版本 解压之后的结果      第二步:安装Redis     1.键入cmd   2.输入:D:      3.指向redis安装路径 cdSoftWareInstall\Redis   这里输入您的Redis解压路径   &nb

  • zk框架中利用map类型传值来创建window,并且传值

    1@Command 2@NotifyChange("accList") 3publicvoidclear(@BindingParam("id")StringaccountId){ 4Map<String,String>arg=newHashMap<String,String>(); 5arg.put("accountId",accountId); 6Windowwin=(Window)Executions.getCurrent().createComponents( 7"/person/changePassword.zul",null,arg); 8win.setClosable(true); 9win.doModal(); 10}复制 zk框架中利用map类型传值来创建window,并且传值 转载请注明文章来自:程序猴(http://www.chengxuhou.com/)

  • SpringBoot事务注解@Transactional 事物回滚、手动回滚事物

    处理springboot下提交事务异常,数据库没有回滚的问题。 spring的文档中说道,spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。 什么是检查型异常什么又是非检查型异常?最简单的判断点有两个:1.继承自runtimeexception或error的是非检查型异常,而继承自exception的则是检查型异常(当然,runtimeexception本身也是exception的子类)。   2.对非检查型类异常可以不用捕获,而检查型异常则必须用try语句块进行处理或者把异常交给上级方法处理总之就是必须写代码处理它。所以必须在service捕获异常,然后再次抛出,这样事务方才起效。   结论: 在spring的事务管理环境下,使用unckecked exception可以极大地简化异常的处理,只需要在事务层声明可能抛出的异常(这里的异常可以是自定义的unckecked exception体系),在所有的中间层都只是需要简单throws即可,不需要捕捉和处理,直接到最高层,比如UI层再进行异常的捕捉和处理。 &nb

  • python爬虫练习5——新闻联播

    提取新闻联播相关文字并输出 网址:新闻联播(cctv.com) importrequests importre url='https://tv.cctv.com/lm/xwlb/' ua={'User-Agent':'Mozilla/5.0(WindowsNT6.1;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.114Safari/537.36Edg/91.0.864.59'} r=requests.get(url,headers=ua,timeout=30) r.encoding=r.apparent_encoding pat1='<li><ahref="(.*?)"target' url=re.compile(pat1,re.S).findall(r.text)[2] r=requests.get(url,headers=ua,timeout=30) r.encoding=r.apparent_encoding pat2='descriptioncontent="(.*?)

  • 课后作业-阅读任务-阅读提问-4

    暂时没有问题

  • C语言学习笔记 初识函数

    1. 函数定义: 2. 函数返回: 从函数中返回return停止函数的执行,并返回一个值。 return;或return表达式; 没有返回值的函数void函数名(参数表)复制 3. 函数原型: #include<stdio.h> voidsum(intbegen,intend);//声明 intmain() { sum(1,10); return0; } voidsum(intbegen,intend)//定义 { //函数体; }复制 4. 参数传递 类型不匹配。调用函数时给的值与参数的类型不匹配时,C语言编译器总是偷偷地替你把类型转换好,但是这可能不是你所期望的, 后续的语言,C++和Java在这方面很严格。 voidcheer(inti) { printf("cheer%d",i); } intmain() { cheer(2.4); return0; } //warningC4244:“函数”:从“double”转换到“int”,可能丢失数据 //cheer2复制 传值: voidswap(inta,intb);

  • elementUi @change等事件传多个参数,自定义传值

    在element-ui中,自带的事件的参数是返回它默认的(如下图),但是有时候我们需要传一些自定义的参数,这里提供两种方法:       使用$event:@change="test1($event,123)" 使用回调函数:@change="((val)=>{test2(val,456)})" 这里是el-select和el-switch 的使用例子: <template> <div>  //使用$event <el-selectv-model="value1"placeholder="请选择"@change="test1($event,123)"> <el-optionv-for="iteminoptions":key="item.value":label="item.label":value="item.value"> </el-option> </el-select>   //使用箭头函数 <el-switchv-model="v

  • Windows netstat 查看端口、进程占用

    目标:在Windows环境下,用netstat命令查看某个端口号是否占用,为哪个进程所占用. 操作:操作分为两步:(1)查看该端口被那个PID所占用;方法一:有针对性的查看端口,使用命令 Netstat–ano|findstr“<端口号>”,如图,最后一列为PID。图中的端口号为1068,所对应的PID为3840。                       (a)图 方法二:查看所有的,然后找到对应的端口和PID。   (b)图 第一幅图中的5列就是上面(a)图对应的5列 (2)查看该PID对应的进程名称。 方法一:一直用命令查找,tasklist|findstr“<PID号>”    (c)图 从(c)图可以看出,PID为3840所对应的进程名字为msnmsgr.exe。 方法二:用任务管理器查看。 调出任务管理器,选择列

  • Linux文件系统,硬链接、软链接、iNode、dentry

    http://daoluan.net/blog/inode-vnode-dentry/ 传统的Unix既有v节点(vnode)也有i节点(inode),vnode的数据结构中包含了inode信息。但在Linux中没有使用vnode,而使用了通用inode。“实现虽不同,但在概念上是一样的。”vnode(“virtualnode”)仅在文件打开的时候,才出现的;而inode定位文件在磁盘的位置,它的信息本身是存储在磁盘等上的,当打开文件的时候从磁盘上读入内存。 inode信息就存储在磁盘的某个分区上。下图是上图的一个扩展:inode指示了文件在数据块中的物理位置。所以仅仅存在inode无法描述一个完整的文件系统,比如:目录与目录的树状结构,这一点在inode上无法体现。延伸: 如果多个inode指向同一个数据块的时候,是不是就可以实现熟悉的链接了?!这就是软连接的原理,新建一个文件(一个符号链接文件,文件的属性中有明确说明它是一个符号链接文件),为需要链接的文件分配一个新的inode,然后指向同一个文件。所以删除软连接文件不会真正删除源文件,而删除源文件过后,软连接文件将失效

相关推荐

推荐阅读