DOTS:Burst

目录

一:简介

1.1 Getting started

1.2 C# language support

1.2.1 HPC# overview

1.2.1.1 Exception expressions

1.2.1.2 Foreach and While

1.2.1.3 Unsupported C# features in HPC#

1.2.2 Static read-only fields and static constructor support

1.2.3 String support

1.2.4 Function Pointers

1.2.4.1 Using function pointers

1.2.4.2 Performance表现

1.2.5 C#/.NET type support

1.2.6 C#/.NET System namespace support

1.2.7 DllImport and internal calls

1.2.8 SharedStatic struct

2.1 Burst intrinsics overview

2.1.3 简介

2.1.1 SIMD 和 SISD:

2.1.2 CPU单核和多核:

2.1.4 Burst Intrinsics Common class

3.1 Editor Reference

3.1.1 Burst menu reference

3.1.1.1 Show Timings setting

3.1.2 Burst Inspector window reference

3.2 Compilation

3.2.1 Synchronous compilation

3.2.2 BurstCompile attribute

3.2.3 BurstDiscard

3.2.4 Generic jobs

3.2.5 Compilation warnings

4.1 Building your project

5.1 Optimization

5.1.1 Debugging and profiling tools

5.1.1.1 Profiling Burst-compiled code

5.1.2 Loop vectorization

5.1.3 Memory aliasing

5.1.3.1 No Alias attribute

5.1.3.2 Aliasing and the job system

5.1.4 AssumeRange attribute

5.1.5 Hint intrinsics​​​​​​​


一:简介

  • 不使用Burst的编译流程:
    • 不使用IL2CPP:
      • c#->Roslyn编译器编译成IL字节码->运行时通过Mono虚拟机转换成目标平台的机器码
    • 使用IL2CPP:
      • c#->Roslyn 编译器编译成IL字节码->IL2CPP编译器转换成C++代码->特定平台的编译器,编译成特定平台的机器码(绕过了mono虚拟机,增加了安全性,快捷性,裁剪了无用代码)
  • Burst是一个编译器,封装了LLVM编译器,把IL字节码,编译成优化后的机器码,它专注于优化那些通过Unity的Job System和ECS编写的高性能代码片段
  • IL2CPP和Burst编译器可以并行工作:IL2CPP负责将项目中的大部分C#代码(包括Unity脚本、第三方库等)转换成本地机器代码,以便在不同平台上运行。

"本地机器码"通常指的就是CPU可以直接执行的指令,也被称为"CPU字节码"或简单地说是"机器码"。这些指令是针对特定CPU架构设计的,比如x86, ARM等,它们由一系列的二进制代码组成,这些代码可以直接被CPU解读和执行。 不同的CPU架构有不同的指令集,即它们能理解和执行的机器码指令集合。因此,将程序编译成本地机器码意味着它被转换成了特定CPU架构能够直接执行的指令序列。这是为什么同一个高级语言编写的程序(如C#或C++)需要为不同的目标平台(如Windows上的x86或Android上的ARM)分别编译的原因。

1.1 Getting started

Burst 主要用来和Unity's job system一起使用,为Job struct 添加[BurstCompile]属性, 或者给类型,静态方法,添加[BurstCompile]属性,标记改方法或者改类型,使用Burst编译器。

 // Using BurstCompile to compile a Job with Burst

    [BurstCompile]
    private struct MyJob : IJob
    {
        [ReadOnly]
        public NativeArray<float> Input;

        [WriteOnly]
        public NativeArray<float> Output;

        public void Execute()
        {
            float result = 0.0f;
            for (int i = 0; i < Input.Length; i++)
            {
                result += Input[i];
            }
            Output[0] = result;
        }
    }

1.2 C# language support

Burst 使用了C#的一个高性能子集,叫作High Performance C# (HPC#) ,它与c#有很多限制和区别

1.2.1 HPC# overview

HPC#支持c#中的大多数表达式和语句。它支持以下功能:

Supported featureNotes
Extension methods.支持扩展方法
Instance methods of structs.支持结构体的实例方法
Unsafe code and pointer manipulation.unsafe的code和指针
Loading from static read-only fields.Static read-only fields and static constructors.
Regular C# control flows.if else、switch case、for while  break continue
ref and outparameters支持ref、out
fixed支持fixed关键字,表示在fixed块被执行完之前,不能被垃圾回收,内存被固定
Some IL opcodes.cpblk、initblk、sizeof
DLLImport and internal calls. DLLImport and internal calls.

try、finally关键字、IDisposable

using、foreach

Burst如果发生异常,和c#表现不同,c#会执行的finally块,burst不会执行到finally块,而是会抛出来

foreach支持特定的示例

Strings、ProfilerMarker.Support for Unity Profiler markers.
throwexpressions.

Burst 只支持简单的throw模式, 比如:

throw new ArgumentException("Invalid argument").  Burst 会提取异常的静态字符串消息并将其包含在生成的代码中。

Strings and Debug.Log.String support and Debug.Log.

Burst还提供了HPC#不能直接访问的C#方法的替代方案:

  • Function pointers 替代委托
  • Shared static 可以访问可以修改的静态字段
1.2.1.1 Exception expressions

Burst支持throw exception.在editor下可以捕捉,在运行时,就会crash,所以要确保exception被捕捉到,通过给方法添加[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]捕捉异常,如果不添加,会有警告:

Burst warning BC1370: An exception was thrown from a function without the correct [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] guard. Exceptions only work in the editor and so should be protected by this guard

1.2.1.2 Foreach and While

Burst 对foreach、while 某种情况下不支持 - 采用一个或多个泛型集合参数的方法 T: IEnumerable<U> 不支持:

public static void IterateThroughConcreteCollection(NativeArray<int> list)
{
    foreach (var element in list)
    {
        // This works
    }
}

public static void IterateThroughGenericCollection<S>(S list) where S : struct, IEnumerable<int>
{
    foreach (var element in list)
    {
        // This doesn't work
    }
}

IterateThroughConcreteCollection()参数是一个确定的类型 NativeArray<int>. IterateThroughGenericCollection() 参数是一个泛型参数,它的代码,不会被Burst编译器编译:

Can't call the method (method name) on the generic interface object type (object name). This may be because you are trying to do a foreach over a generic collection of type IEnumerable.

1.2.1.3 Unsupported C# features in HPC#

HPC# 不支持

  • try/catch的catch
  • 存储到静态字段,或者使用 Shared Static
  • 任何关于托管对象的方法, for example, string methods.

1.2.2 Static read-only fields and static constructor support

不支持静态的非只读的数据,因为只读的静态数据,在编译时,就会替换了,如果编译失败,就会替换成默认值

1.2.3 String support

Burst支持下面几种string的用法: 

  • Debug.Log,支持字符串内插,内插的值必须是value type,除了half
  • Unity.Collection里的FixedString 结构体,比如:FixedString128Bytes.
  • System.Runtime.CompilerServices 属性[CallerLineNumber][CallerMemberName],[CallerFilePath] 

字符串不能传递给方法,或者作为struct的字段.可以使用 Unity.Collections库里面的FixedString结构体:

int value = 256;
FixedString128 text = $"This is an integer value {value} used with FixedString128";
MyCustomLog(text);

// String can be passed as an argument to a method using a FixedString, 
// but not using directly a managed `string`:
public static void MyCustomLog(in FixedString128 log)
{
    Debug.Log(text);
}

1.2.4 Function Pointers

使用 FunctionPointer<T>替代c#的委托,因为delegates是托管对象,所以Burst不支持

Function pointers 不支持泛型委托. 也不要在另一个泛型方法中封装BurstCompiler.CompileFunctionPointer<T> ,否则,Burst不会生效,比如代码优化,安全检查

1.2.4.1 Using function pointers
  • 在类上添加[BurstCompile] 属性
  • 在类的静态方法上添加[BurstCompile] 属性
  • 声明一个委托,标记这些静态方法
  • 在绑定委托的方法上添加[MonoPInvokeCallbackAttribute]属性. 这样IL2CPP才能正常使用
  1. // Instruct Burst to look for static methods with [BurstCompile] attribute
    [BurstCompile]
    class EnclosingType {
        [BurstCompile]
        [MonoPInvokeCallback(typeof(Process2FloatsDelegate))]
        public static float MultiplyFloat(float a, float b) => a * b;
    
        [BurstCompile]
        [MonoPInvokeCallback(typeof(Process2FloatsDelegate))]
        public static float AddFloat(float a, float b) => a + b;
    
        // A common interface for both MultiplyFloat and AddFloat methods
        public delegate float Process2FloatsDelegate(float a, float b);
    }
    
  • 然后在C#中声明这些委托:
    FunctionPointer<Process2FloatsDelegate> mulFunctionPointer =
BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(MultiplyFloat);

    FunctionPointer<Process2FloatsDelegate> addFunctionPointer = 
BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(AddFloat);
  • 在job中使用:
    // Invoke the function pointers from HPC# jobs
    var resultMul = mulFunctionPointer.Invoke(1.0f, 2.0f);
    var resultAdd = addFunctionPointer.Invoke(1.0f, 2.0f);

Burst默认以异步方式编译function pointers,[BurstCompile(SynchronousCompilation = true)]强制同步编译

在C# 中使用function point,最好先缓存FunctionPointer<T>.Invoke 属性,它就是委托的一个实例:

    private readonly static Process2FloatsDelegate mulFunctionPointerInvoke = 
BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(MultiplyFloat).Invoke;

    // Invoke the delegate from C#
    var resultMul = mulFunctionPointerInvoke(1.0f, 2.0f);
1.2.4.2 Performance表现

最好在job里面使用function pointer,Burst为job提供了better aliasing calculations

不能给function pointers直接传递[NativeContainer] 结构体,比如NativeArray,必须使用job struct,Native container包含了用于安全检查safety check的托管对象。

下面是一个不好的例子:

///Bad function pointer example
[BurstCompile]
public class MyFunctionPointers
{
    public unsafe delegate void MyFunctionPointerDelegate(float* input, float* output);

    [BurstCompile]
    public static unsafe void MyFunctionPointer(float* input, float* output)
    {
        *output = math.sqrt(*input);
    }
}

[BurstCompile]
struct MyJob : IJobParallelFor
{
     public FunctionPointer<MyFunctionPointers.MyFunctionPointerDelegate> FunctionPointer;

    [ReadOnly] public NativeArray<float> Input;
    [WriteOnly] public NativeArray<float> Output;

    public unsafe void Execute(int index)
    {
        var inputPtr = (float*)Input.GetUnsafeReadOnlyPtr();
        var outputPtr = (float*)Output.GetUnsafePtr();
        FunctionPointer.Invoke(inputPtr + index, outputPtr + index);
    }
}

不好的点在于:

  • Burst不能矢量化function pointer ,因为它的参数是标量,这会损失4-8倍的性能
  • MyJob知道InputOutput是native arrays不能alisa,但是function pointer不知道
  • There is a non-zero overhead to constantly branching to a function pointer somewhere else in memory.
[BurstCompile]
public class MyFunctionPointers
{
    public unsafe delegate void MyFunctionPointerDelegate(int count, float* input, float* output);

    [BurstCompile]
    public static unsafe void MyFunctionPointer(int count, float* input, float* output)
    {
        for (int i = 0; i < count; i++)
        {
            output[i] = math.sqrt(input[i]);
        }
    }
}

[BurstCompile]
struct MyJob : IJobParallelForBatch
{
     public FunctionPointer<MyFunctionPointers.MyFunctionPointerDelegate> FunctionPointer;

    [ReadOnly] public NativeArray<float> Input;
    [WriteOnly] public NativeArray<float> Output;

    public unsafe void Execute(int index, int count)
    {
        var inputPtr = (float*)Input.GetUnsafeReadOnlyPtr() + index;
        var outputPtr = (float*)Output.GetUnsafePtr() + index;
        FunctionPointer.Invoke(count, inputPtr, outputPtr);
    }
}

好的点在于:

  • Burst 矢量化了 MyFunctionPointer方法.
  • Burst 在每一个function pointer 处理count个item,调用函数指针的任何开销都减少了count次.
  • 批处理的性能比不批处理的性能提高1.53倍。

Burst使用IL Post Processing自动把代码,转成function pointer调用

但是最好还是:

[BurstCompile]
struct MyJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<float> Input;
    [WriteOnly] public NativeArray<float> Output;

    public unsafe void Execute(int index)
    {
        Output[i] = math.sqrt(Input[i]);
    }
}

addDisableDirectCall = true可以关闭自动转换

[BurstCompile]
public static class MyBurstUtilityClass
{
    [BurstCompile(DisableDirectCall = true)]
    public static void BurstCompiled_MultiplyAdd(in float4 mula, in float4 mulb, in float4 add, out float4 result)
    {
        result = mula * mulb + add;
    }
}

1.2.5 C#/.NET type support

Burst使用 .NET的一个字集,不允许使用任何托管对象或者引用类型,比如class

  • 内置类型
    • 支持的:
      • bool
      • byte/sbyte
      • double
      • float
      • int/uint
      • long/ulong
      • short/ushort
    • 不支持的:
      • char
      • decimal
      • string :因为是托管类型
  • 数组类型:
    • 支持的
      • 静态只读的数组
      • 不能作为方法的参数
      • C#不使用job的代码,不能更改数组的数据,也就是只有job里面才可以更改,因为 Burst 编译器会在编译的时候copy一份数据.
    • 不支持的
      • 不支持多维数组
      • 不支持托管数据,但可以使用NativeArray
  • 结构体类型
    • 支持的
      • 含有上面类型的常规结构体
      • 具有固定长度数组的结构体,就是数组一开始就声明长度
      • 具有explicit layout的结构体可能不会生成最优代码
        • 支持的layout:
          • LayoutKind.Sequential
          • LayoutKind.Explicit
          • StructLayoutAttribute.Pack
          • StructLayoutAttribute.Size
      • 支持含有System.IntPtr、System.UIntPtr字段,作为原生属性
  • Vector类型
    • Burst会把  Unity.Mathematics 的vector类型转换成适合SIMD vector类型 :
      • bool2/bool3/bool4
      • uint2/uint3/uint4
      • int2/int3/int4
      • float2/float3/float4
      • 优先使用bool4uint4float4int4类型
  • 枚举类型
    • 支持
      • 常规类型以及带有特定存储类型的类型,比如:public enum MyEnum : short
    • 不支持
      • 不支持枚举类型的方法,比如: Enum.HasFlag
         
  • 指针类型
    • 支持所有支持类型的指针

1.2.6 C#/.NET System namespace support

Burst 会把system命名空间下变量的转换成与Burst兼容的变量

  • System.Math
    • 支持 System.Math下所有的方法:
    • double IEEERemainder(double x, double y)支持持 .NET Standard 2.1以上
  • System.IntPtr
    • 支持 System.IntPtr/System.UIntPtr下的所有方法,包括静态字段IntPtr.Zero、IntPtr.Size
  • System.Threading.Interlocked
    • Burst支持System.Threading.Interlocked下的所有方法,即线程安全(比如 Interlocked.Increment).

确保interlocked methods的source 位置是对齐的,比如:指针的对齐方式是指向类型的倍数:

[StructLayout(LayoutKind.Explicit)]
struct Foo
{
    [FieldOffset(0)] public long a;
    [FieldOffset(5)] public long b;

    public long AtomicReadAndAdd()
    {
        return Interlocked.Read(ref a) + Interlocked.Read(ref b);
    }
}
  • System.Threading.Thread
    • 支持其中的 MemoryBarrier方法
  • System.Threading.Volatile
    • Burst 支持非泛型的变量Read、Write方法,该参数表示多个线程共享改变

1.2.7 DllImport and internal calls

调用native plugin下面的方法,使用 [DllImport]:

[DllImport("MyNativeLibrary")]
public static extern int Foo(int arg);

Burst也支持Unity内部实现的的内部调用:

// In UnityEngine.Mathf
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern int ClosestPowerOfTwo(int value);

DllImport仅支持native plug-ins下的方法,不支持独立于平台的dll,比如:kernel32.dll.

DllImport支持的类型有:

TypeSupported type
Built-in and intrinsic typesbyte / sbyte
short / ushort
int / uint
long / ulong
float
double
System.IntPtr / System.UIntPtr
Unity.Burst.Intrinsics.v64 / Unity.Burst.Intrinsics.v128 / Unity.Burst.Intrinsics.v256
Pointers and referencessometype* : 指针类型
ref sometype : 引用类型
Handle structsunsafe struct MyStruct { void* Ptr; } : 只包含一个指针类型的Struct 
unsafe struct MyStruct { int Value; } : 只包含一个整数类型的Struct 

需要通过指针或者引用传递structs,不能通过值类型传递,除了上面支持的类型,handle structs

1.2.8 SharedStatic struct

如果想在C#、HPC#共享静态的可变数据,使用 SharedStatic<T>

    public abstract class MutableStaticTest
    {
        public static readonly SharedStatic<int> IntField = SharedStatic<int>.GetOrCreate<MutableStaticTest, IntFieldKey>();

        // Define a Key type to identify IntField
        private class IntFieldKey {}
    }

C#、HPC# 都可以通过下面访问:

    // Write to a shared static 
    MutableStaticTest.IntField.Data = 5;
    // Read from a shared static
    var value = 1 + MutableStaticTest.IntField.Data;

使用SharedStatic<T>时,需要注意:

  • T in SharedStatic<T> 定义了数据类型
  • 为了识别static 字段,提供一个上下文,为两个包含类型都创建一个键,比如:MutableStaticTest、 IntFieldKey 
  • 在从hpc#访问共享静态字段之前,现在c#里面初始化它

2.1 Burst intrinsics overview

2.1.3 简介

Burst 提供了低阶的Api,在 Unity.Burst.Intrinsics 命名空间下,如果想写SIMD程序集代码,可以使用它下面的代码,获取额外的性能,就类似于底层代码。

2.1.1 SIMD 和 SISD:

  • SISD单指令,单数据,一条指令如果执行多条数据,是串行的
  • SIMD单指令,多数据,一条指令如果执行多条数据,是并行的
  • 不同点:处理器不同,SIMD的处理器,能够处理多条数据
  • 相同点:都会有指令集存储器,和寄存器来保存数据

2.1.2 CPU单核和多核:

  • 单核:一个中央处理单元,一个核心,处理任务,只能一个人干,串行,单核提升速度的方法:提高时钟频率
  • 多核:一个中央处理单元,多个核心,处理任务,多个人干,并行。比如:多个应用开启多个线程,就可以多个核心一起干
  • 时钟频率:单位Hz,表示一秒执行多少次周期,比如3GHz,表示一秒进行30亿次,一般说Hz越高,运行速度越快,一条指令可能需要多个周期,单核的时候,单纯的提高时钟频率,会增加耗能、发热
    • HZ的单位有:
      • - KHz(千赫兹):1 kHz = 10^3 Hz(一千赫兹)
      • - MHz(兆赫兹):1 MHz = 10^6 Hz(一百万赫兹)
      • - GHz(吉赫兹):1 GHz = 10^9 Hz(十亿赫兹)
      • - THz(太赫兹):1 THz = 10^12 Hz(一万亿赫兹)

2.1.4 Burst Intrinsics Common class

Unity.Burst.Intrinsics.Common 提供了在Burst支持的硬件上通用的功能。

  • Unity.Burst.Intrinsics.Common.Pause :CPU 暂停当前线程,在x86上pause,在ARM上yield,在多线程编程中,尤其是在实现自旋锁(spinlock)或者在等待某个条件变为真时,直接使用忙等待循环(即不断地检查条件是否满足,而不进行休眠)会导致CPU在这段时间内高速运行,消耗大量的处理器资源。如果使用`Pause`指令,它会提示CPU在这种忙等待的场景下稍微“放慢脚步”,这样可以减少对CPU资源的消耗,同时对于等待的线程来说,延迟的增加是非常小的,几乎可以忽略不计​​​​​​​
  • 自旋锁:
    • ​​​​​​​自旋锁(Spinlock)是一种用于多线程同步的锁机制,主要用于保护共享资源或临界区。与传统的锁(如互斥锁)不同,当一个线程尝试获取一个已经被其他线程持有的自旋锁时,它不会立即进入休眠状态(即阻塞状态),而是在锁被释放之前,持续在一个循环中检查锁的状态(这个过程称为“自旋”)。这意味着线程会一直占用CPU进行循环,直到它能够获取到锁。
    • 自旋锁的优点:
      • 当锁被占用的时间非常短时,它可以避免线程的上下文切换开销,因为线程不会进入休眠状态。这使得自旋锁在某些情况下比传统的锁更高效,尤其是在多核处理器上处理高并发且锁持有时间短的场景。
    • 缺点:
      • 1. **CPU资源消耗**:自旋锁在等待锁释放期间会持续占用CPU,如果锁被长时间持有,这将导致大量的CPU资源浪费。
      • 2. **不适用于单核处理器**:在单核处理器上,自旋锁可能导致更差的性能,因为持有锁的线程可能无法释放锁,因为等待锁的线程持续占用CPU不让出执行机会。
      • 3. **饥饿问题**:在某些情况下,自旋锁可能导致线程饥饿,即某些线程可能永远无法获取到锁,因为总有其他线程比它更早地获取到锁。
      • 因此,自旋锁的使用需要仔细考虑其适用场景,通常是在多核处理器、锁持有时间非常短且对性能要求极高的情况下。在其他情况下,可能需要考虑使用其他类型的锁或同步机制
  • 互斥锁:
    • 当一个线程尝试获取一个已经被其他线程持有的锁时,该线程会进入阻塞状态。在这种情况下,操作系统会进行线程上下文切换,将CPU的控制权转移给另一个线程这个过程涉及到保存当前线程的状态(例如寄存器、程序计数器等)并恢复另一个线程的状态,以便另一个线程可以继续执行。线程上下文切换是一个相对昂贵的操作,因为它涉及到一系列的硬件和操作系统层面的操作
  • Unity.Burst.Intrinsics.Common.Prefetch 是一个实验性的内在特性,用于将特定的内存地址上的数据预加载到CPU缓存中,目的是为了在实际访问这些数据之前减少访问延迟,从而提高执行效率。使用prefetch可以在进行大量内存读取操作的循环中提高性能,尤其是对于那些访问模式可预测的循环操作。然而,正确地使用prefetch需要对所处理数据的内存访问模式有很好的理解,错误的使用可能不会带来任何性能上的改善,甚至可能使性能更差。因为是实验行的,所以要UNITY_BURST_EXPERIMENTAL_PREFETCH_INTRINSIC来访问
  •  Unity.Burst.Intrinsics.Common.umul128:用于执行无符号的128位乘法操作。这个函数接受两个64位无符号整数作为输入,执行它们的乘法,并返回128位的乘积结果。由于直接在C#中进行128位整数运算并不直接支持,这个函数提供了一种在需要进行大数乘法时的有效手段,尤其是在性能敏感的应用场景中。
    • 具体来说,`umul128`函数会返回一个包含两个64位无符号整数的元组或结构体,这两个整数分别代表乘积的低64位和高64位。这样,开发者可以在不丢失精度的情况下处理大于64位的乘法运算结果。
    • 使用`umul128`可以在进行大整数运算、加密算法、随机数生成等需要高精度和大范围数值计算的场景中非常有用。然而,由于它是一个低级的内联函数,使用时需要对数字运算有一定的理解,以确保正确处理乘法的结果。
  • Unity.Burst.Intrinsics.Common.InterlockedAnd 和Unity.Burst.Intrinsics.Common.InterlockedOr 提供了int,uint,long,ulong类型的原子属性上的且或操作,因为是实验行的,使用宏UNITY_BURST_EXPERIMENTAL_ATOMIC_INTRINSICS声明访问

3.1 Editor Reference

3.1.1 Burst menu reference

Enable Compilation Burst编译带有 [BurstCompile]的 jobs 和自定义 delegates
Enable Safety ChecksEnable Safety Checks setting 
Off关闭安全检查在jobs和function-pointers上,可以获取额外的真实的性能
On对collection containers (e.g NativeArray<T>)开启安全检查,包括job data依赖和是否越界
Force On即使 DisableSafetyChecks = true也安全检查
Synchronous CompilationSynchronous compilation.
Native Debug Mode Compilation关闭burst对代码的优化 Native Debugging tools.
Show Timings显示burst编译的时间 Show Timings setting 
Open InspectorOpens the Burst Inspector window.
3.1.1.1 Show Timings setting

开启Show Timings时,Unity会打印出来,Burst编译每个库的入口点时间,Burst会把一个程序集的所有方法,编译成一个单元,批处理,把多个entry-points成组,组成一个task.

Burst的工作主要分为下面几步:

  • 找出需要burst编译的所有方法
  • front end找到之后,Burst将c# IL转换为LLVM IR模块
  • middle end然后Burst specializes, optimizes, cleans up 
  • back end最后Burst把LLVM IR module转换成native DLL)

front end编译的时间,和需要编译的方法,成正比关系,泛型越多,时间越长,因为每个类型都编译一遍

back-end 的时间与entry-point的数量成正比,以为每一个entry point都是一个单独的文件。比如一个脚本。

如果optimize花费了大量的时间,通过[BurstCompile(OptimizeFor = OptimizeFor.FastCompilation)]可以减少优化的的内容,同时也会变快。

3.1.2 Burst Inspector window reference

Burst Inspector窗口展示了所有Burst编译的jobs和其它对象,Jobs > Burst > Open Inspector.

3.2 Compilation

  • 在Play mode,Burst通过just-in-time (JIT)编译,异步编译,表示在Burst编译完之前,都是使用Mono编译器编译的代码,如果不想异步编译,请看Synchronous compilation
  • 在发布的时候,Burst通过ahead-of-time (AOT)编译,通过Playersetting窗口,控制编译的方式,详情:Building your project

3.2.1 Synchronous compilation

默认,Burst异步编译jobs,在play mode 模式下,通过 CompileSynchronously = true,表示同步编译。

[BurstCompile(CompileSynchronously = true)]
public struct MyJob : IJob
{
    // ...
}

如果不设置的话,当第一次运行这个job时,Burst 在后台线程中异步编译,与此同时,运行的是托管的c#代码。

CompileSynchronously = true,它会影响当前帧,体验不好,一般在下面情况中使用:

  • 当想测试Burst编译后的代码时,同时忽略首次调用的时间
  • 调试托管和编译代码之间的差异。

3.2.2 BurstCompile attribute

  • 对数学方法使用不同的精度,比如sin,cos.
  • 放松对数学计算的限制,这样burst就可以重新安排浮点数的计算顺序
  • 强迫synchronous compilation of a job 
[BurstCompile(FloatPrecision.Med, FloatMode.Fast)]
  • FloatPrecision:单位ulp,表示浮点数之间的空间
    • FloatPrecision.Standard: 和FloatPrecision.Medium一样,精度 3.5 ulp.
    • FloatPrecision.High:  1.0 ulp.
    • FloatPrecision.Medium: 3.5 ulp.
    • FloatPrecision.Low: 每个函数都定义了精度,函数可以指定有限范围的有效输入。
  • FloatMode
    • FloatMode.Default: 和FloatMode.Strict一样.
    • FloatMode.Strict: 不执行重排计算的顺序
    • FloatMode.Fast: 可以重排顺序,对于不需要精确的计算顺序可以使用
    • FloatMode.Deterministic: 为后面版本预留的
  • ulp
    • Unit in the Last Place,表示相邻两个浮点数之间的距离,它的大小取决于浮点数的精度和大小。 举个例子,假设我们有两个浮点数A和B,A < B,那么A和B之间的“空间”可以用它们之间相差的ULP数来描述。如果A和B之间正好相差1个ULP,那么意味着没有其他浮点数能够位于A和B之间;如果它们之间相差多个ULP,那么就存在其他浮点数可以位于A和B之间,指的是两个数值之间的差距,例如,比较两个浮点数是否接近可能需要计算它们之间的ULP差异,而不是直接比较它们的值。
给整个程序集添加burst编译属性
[assembly: BurstCompile(CompileSynchronously = true)]

3.2.3 BurstDiscard

添加上此属性,代码不会被Burst编译,也就是在job标价burst时,标记该属性的方法不会执行,添加该属性的方法,不能有返回值,可以通过ref、out来获取更改后的值

[BurstCompile]
public struct MyJob : IJob
{
    public void Execute()
    {
        // Only executed when running from a full .NET runtime
        // this method call will be discard when compiling this job with
        // [BurstCompile] attribute
        MethodToDiscard();
    }

    [BurstDiscard]
    private static void MethodToDiscard(int arg)
    {
        Debug.Log($"This is a test: {arg}");
    }
}

[BurstDiscard]
private static void SetIfManaged(ref bool b) => b = false;

private static bool IsBurst()
{
    var b = true;
    SetIfManaged(ref b);
    return b;
}

3.2.4 Generic jobs

不支持嵌套job,如果使用了嵌套job,在editor下,burst能检测到,并使用burst编译,但是在build时,burst不会对这部分代码编译,所以editor下和运行时,两者的性能有差距。

比如:

直接使用泛型job
[BurstCompile]
struct MyGenericJob<TData> : IJob where TData : struct { 
    public void Execute() { ... }
}


或者包装一层,job不是泛型的,但可以使用Tdata数据
public class MyGenericSystem<TData> where TData : struct {
    [BurstCompile]
    struct MyGenericJob  : IJob { 
        public void Execute() { ... }
    }

    public void Run()
    {
        var myJob = new MyGenericJob(); // implicitly MyGenericSystem<TData>.MyGenericJob
        myJob.Schedule();    
    }
}

---------------------------------

嵌套类型,外部是泛型,内部也是泛型
public static void GenericJobSchedule<TData>() where TData: struct {
    // Generic argument: Generic Parameter TData
    // This Job won't be detected by the Burst Compiler at standalone-player build time.
    var job = new MyGenericJob<TData>();
    job.Schedule();
}

只能在editor下使用burst编译
GenericJobSchedule<int>();

3.2.5 Compilation warnings

使用 Unity.Burst.CompilerServices.IgnoreWarningAttribute可以忽略警告

  • BC1370
    • An exception was thrown from a function without the correct [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]guard...
    • 如果使用了throw,但是没有catch,就会报这个,因为throw在运行时,会崩溃,加上[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]属性,throw的方法在build时就会丢弃
  • BC1371
    • 当一个方法,被discard时,会报

4.1 Building your project

在build时,burst会编译代码,然后把它编译成一个动态链接库(dll),放在plugin文件夹下面,不同的平台,放的位置不一样,比如window:Data/Plugins/lib_burst_generated.dll

iOS例外,它生成的是一个静态库

job在运行时compile代码时,会首先加载dll,通过Edit > Player Settings > Burst AOT Settings设置Burst编译时的设置

5.1 Optimization

5.1.1 Debugging and profiling tools

  • Editor:
    • 可以使用rider、vs自带的debug工具,attach之后,unity会关闭burst优化,现在debug的就是托管代码。
    • 也可以使用native debug工具,比如vs、xcode,同样会关闭burst优化,有两种方式:
      • 一种是:开启Jobs > Burst > Native Debug Mode Compilation,它会关闭所有的burst优化
      • ​​​​​​​​​​​​​​​​​​​​​一种是:[BurstCompile(Debug = true)]只对某个job,关闭burst优化
  • Player Mode
    • 需要给debug tool指定burst生成的符号文件,一般是在plugin文件夹,在这之前需要开启生成符号文件的选项,有两种方式:
      •  Development Build 
      •  Burst AOT Player Settings开启Force Debug Information
      • 同时需要关闭 Burst optimizations,有两种方式:​​​​​​​
        • Debug = true,关闭指定的job
        • 关闭Burst AOT Player Settings,Enable Optimizations选项,是关闭所有job
  • System.Diagnostics.Debugger.Break System.Diagnostics.Debugger.Break 方法,可以在debuger attach的时候,触发,其它的时候不触发,就相当于断点
5.1.1.1 Profiling Burst-compiled code

想要分析burst编译后的代码,可以在 Instruments 或者 Superluminal里,分析编译后的代码,前提先指定,burst编译后的符号文件

可以通过playermarker,对debug的代码做标记:

[BurstCompile]
private static class ProfilerMarkerWrapper
{
    private static readonly ProfilerMarker StaticMarker = new ProfilerMarker("TestStaticBurst");

    [BurstCompile(CompileSynchronously = true)]
    public static int CreateAndUseProfilerMarker(int start)
    {
        using (StaticMarker.Auto())
        {
            var p = new ProfilerMarker("TestBurst");
            p.Begin();
            var result = 0;
            for (var i = start; i < start + 100000; i++)
            {
                result += i;
            }
            p.End();
            return result;
        }
    }
}

5.1.2 Loop vectorization

简单来讲就是把确定了的运算,进行SMID写法处理

[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe void Bar([NoAlias] int* a, [NoAlias] int* b, int count)
{
    for (var i = 0; i < count; i++)
    {
        a[i] += b[i];
    }
}

public static unsafe void Foo(int count)
{
    var a = stackalloc int[count];
    var b = stackalloc int[count];

    Bar(a, b, count);
}


生成的汇编语言

.LBB1_4:
    vmovdqu    ymm0, ymmword ptr [rdx + 4*rax]
    vmovdqu    ymm1, ymmword ptr [rdx + 4*rax + 32]
    vmovdqu    ymm2, ymmword ptr [rdx + 4*rax + 64]
    vmovdqu    ymm3, ymmword ptr [rdx + 4*rax + 96]
    vpaddd     ymm0, ymm0, ymmword ptr [rcx + 4*rax]
    vpaddd     ymm1, ymm1, ymmword ptr [rcx + 4*rax + 32]
    vpaddd     ymm2, ymm2, ymmword ptr [rcx + 4*rax + 64]
    vpaddd     ymm3, ymm3, ymmword ptr [rcx + 4*rax + 96]
    vmovdqu    ymmword ptr [rcx + 4*rax], ymm0
    vmovdqu    ymmword ptr [rcx + 4*rax + 32], ymm1
    vmovdqu    ymmword ptr [rcx + 4*rax + 64], ymm2
    vmovdqu    ymmword ptr [rcx + 4*rax + 96], ymm3
    add        rax, 32
    cmp        r8, rax
    jne        .LBB1_4

汇编语言的意思:

这段代码是使用 x86-64 汇编语言(使用 AVX2 指令集)写的,它对应于提供的 C# 代码,实现了一个简单的向量加法操作。下面是一步一步地解释它的功能:

  • 1. **函数 `Bar` 的循环展开与向量化**: 鉴于源代码的 `Bar` 函数负责将两个整数数组(或指针所指向的内存区域)的元素逐个相加,这段汇编代码采用了向量化的方式来提升性能。它一次处理128个字节(即32个`int`类型的数据,每个`int`占用了4个字节),这是因为使用的 `ymm` 寄存器可以一次性处理256位数据。
  • 2. `vmovdqu ymm0, ymmword ptr [rdx + 4*rax]` 等四条 `vmovdqu` 指令用于从内存中加载数据到 `ymm0`、`ymm1`、`ymm2` 和 `ymm3` 寄存器中。在这里 `[rdx + 4*rax]` 和类似的表达式利用了 `rdx` 作为基址(表示数组 `b` 的起始地址),`rax` 作为索引值(起始值为 0,后续以32为步长递增,代替了循环变量 `i`,因为每次迭代处理32个 `int`),乘以4的原因是每个 `int` 占4字节,用于计算在 `b` 数组中正确的偏移量。
  • 3. `vpaddd     ymm0, ymm0, ymmword ptr [rcx + 4*rax]` 等四条 `vpaddd` 指令执行向量加法(`ymm` 寄存器与内存数据),将 `a` 数组中相应的值(由 `[rcx + 4*rax]` 等地址指定)与 `b` 数组的值相加,结果存回到相应的 `ymm` 寄存器里。
  • 4. 接下来的四条 `vmovdqu` 指令将加法操作的结果 (`ymm0`, `ymm1`, `ymm2`, `ymm3`) 存回到 `a` 数组相应的位置。
  • 5. `add rax, 32` 用于更新循环索引 `i`,跳到下一批次处理的起点,因为每次迭代处理了 32 个 `int`,所以 `rax` 每次增加 32。
  • 6. `cmp r8, rax` 与 `jne .LBB1_4` 这一条件跳转指令组合实现了循环的继续判断。如果 `rax`(代表当前已处理的 `int` 的数量)还没有达到总数 `r8`(`count` 参数的值),汇编执行跳回标签 `.LBB1_4` 开始处理下一批数据。

综上所述,这段汇编代码通过一系列向量化指令并行地完成了数组 `a` 和 `b` 元素的加法操作,显著提高了原始 C# 代码循环中单个元素加法操作的执行效率。

5.1.3 Memory aliasing

它是一种告诉Burst代码中是如何使用数据的。这可以改善和优化应用程序的性能。
当内存中的位置相互重叠(overlap)时,就会发生Memory aliasing。下面概述了Memory aliasing和无Memory aliasing之间的区别。 

[BurstCompile]
private struct CopyJob : IJob
{
    [ReadOnly]
    public NativeArray<float> Input;

    [WriteOnly]
    public NativeArray<float> Output;

    public void Execute()
    {
        for (int i = 0; i < Input.Length; i++)
        {
            Output[i] = Input[i];
        }
    }
}
  • No memory aliasing
    • 如果Input 和 Output 没有发生内存重叠, 就是它们的内存相互独立,就像下面的表示,如果是No Aliasing,Burst就会通过向量化,把已知的的标量给分成批次处理,比如一次处理32个int,而不是单独处理


Memory with no aliasing


Memory with no aliasing vectorized

  • Memory aliasing

如果Output数组Input数组,有元素重叠,比如Output[0]指向Input[1],这就表示内存混叠,比如:

Memory with aliasing


Memory with aliasin

如果没有声明aliasing​​​​​​​,它会自动矢量化,然后结果如下图所示,这样就会有bug,因为内存错位了,数值都变了:


Memory with aliasing and invalid vectorized code

  • Generated code
    • CopyJob,针对x64指令集的子集AVX2生成的汇编语言. 指令vmovups移动8个浮点数,所以一个自动向量化循环移动4 × 8个浮点数,这等于每次循环迭代复制32个浮点数,而不是一个:
.LBB0_4:
    vmovups ymm0, ymmword ptr [rcx - 96]
    vmovups ymm1, ymmword ptr [rcx - 64]
    vmovups ymm2, ymmword ptr [rcx - 32]
    vmovups ymm3, ymmword ptr [rcx]
    vmovups ymmword ptr [rdx - 96], ymm0
    vmovups ymmword ptr [rdx - 64], ymm1
    vmovups ymmword ptr [rdx - 32], ymm2
    vmovups ymmword ptr [rdx], ymm3
    sub     rdx, -128
    sub     rcx, -128
    add     rsi, -32
    jne     .LBB0_4
    test    r10d, r10d
    je      .LBB0_8

同样的代码,但是手动关闭了Burst aliasing,要比原来的性能低:

.LBB0_2:
    mov     r8, qword ptr [rcx]
    mov     rdx, qword ptr [rcx + 16]
    cdqe
    mov     edx, dword ptr [rdx + 4*rax]
    mov     dword ptr [r8 + 4*rax], edx
    inc     eax
    cmp     eax, dword ptr [rcx + 8]
    jl      .LBB0_2
  • Function cloning
    • 对于不知道参数需不需要aliasing的方法,Burst通过copy一份方法副本,然后假设不生成alisa,来生成汇编代码,如果不报错,就替换原来的汇编代码(没有优化的代码)
[MethodImpl(MethodImplOptions.NoInlining)]
int Bar(ref int a, ref int b)
{
    a = 42;
    b = 13;
    return a;
}

int Foo()
{
    var a = 53;
    var b = -2;

    return Bar(ref a, ref b);
}
  • 因为Burst不知道Bar方法里的a和b是否aliasing.,所以生成的汇编语言和其它编译器是一致的:
//dword ptr [rcx]先从rcx(寄存器)取值,然后把42赋值给它,mov
mov     dword ptr [rcx], 42
mov     dword ptr [rdx], 13
//取值rcx,把它赋值给eax
mov     eax, dword ptr [rcx]
//表示控制权结束,return
ret

Burst比这更聪明,通过function cloning,Burst创建了Bar的副本,它推断这个副本中的属性不会发生混叠,生成不发生混叠时的代码,替换原来的调用,这样就不用从寄存器里面取数了

mov     dword ptr [rcx], 42
mov     dword ptr [rdx], 13
mov     eax, 42
ret
  • Aliasing checks
    • 因为aliasing是Burst进行优化的关键,所以提供了一些内部的检测方法:
      • ​​​​​​​​​​​​​​Unity.Burst.CompilerServices.Aliasing.ExpectAliased 表示两个指针会alias,如果没有则生成编译器错误。
      • ​​​​​​​Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased表示两个指针不会alias, 如果不是,则生成编译器错误。

比如:

using static Unity.Burst.CompilerServices.Aliasing;

[BurstCompile]
private struct CopyJob : IJob
{
    [ReadOnly]
    public NativeArray<float> Input;

    [WriteOnly]
    public NativeArray<float> Output;

    public unsafe void Execute()
    {
        // NativeContainer attributed structs (like NativeArray) cannot alias with each other in a job struct!
        ExpectNotAliased(Input.getUnsafePtr(), Output.getUnsafePtr());

        // NativeContainer structs cannot appear in other NativeContainer structs.
        ExpectNotAliased(in Input, in Output);
        ExpectNotAliased(in Input, Input.getUnsafePtr());
        ExpectNotAliased(in Input, Output.getUnsafePtr());
        ExpectNotAliased(in Output, Input.getUnsafePtr());
        ExpectNotAliased(in Output, Output.getUnsafePtr());

        // But things definitely alias with themselves!
        ExpectAliased(in Input, in Input);
        ExpectAliased(Input.getUnsafePtr(), Input.getUnsafePtr());
        ExpectAliased(in Output, in Output);
        ExpectAliased(Output.getUnsafePtr(), Output.getUnsafePtr());
    }
}
5.1.3.1 No Alias attribute

不需要alias,对于native container、job struct,一般不需要添加该属性,因为burst会主动推断是否需要Alisa。

只有那些Burst推断不出来的,可以添加该属性,前提是,明确知道标记的参数,不会进行Alisa,如果标记No Alisa的,实际情况需要Alisa,有可能产生bug

  • 添加Alisa的情况:
    • 方法的参数
    • 方法的返回值
    • 不会Alisa的结构体
    • 不会Alisa的结构体的字段

比如:

int Foo([NoAlias] ref int a, ref int b)
{
    b = 13;
    a = 42;
    return b;
}

----------------------------------------

struct Bar
{
    [NoAlias]
    public NativeArray<int> a;

    [NoAlias]
    public NativeArray<float> b;
}

int Foo(ref Bar bar)
{
    bar.b[0] = 42.0f;
    bar.a[0] = 13;
    return (int)bar.b[0];
}

----------------------------------------

[NoAlias]
unsafe struct Bar
{
    public int i;
    public void* p;
}

float Foo(ref Bar bar)
{
    *(int*)bar.p = 42;
    return ((float*)bar.p)[bar.i];
}

----------------------------------------

[MethodImpl(MethodImplOptions.NoInlining)]
[return: NoAlias]
unsafe int* BumpAlloc(int* alloca)
{
    int location = alloca[0]++;
    return alloca + location;
}

unsafe int Func()
{
    int* alloca = stackalloc int[128];

    // Store our size at the start of the alloca.
    alloca[0] = 1;

    int* ptr1 = BumpAlloc(alloca);
    int* ptr2 = BumpAlloc(alloca);

    *ptr1 = 42;
    *ptr2 = 13;

    return *ptr1;
}
5.1.3.2 Aliasing and the job system

Unity's job system对job中的参数alias有一些限制:

  •  [NativeContainer] (比如 NativeArray and NativeSlice) 不能alisa
  • Job struct中的字段标有 [NativeDisableContainerSafetyRestriction] 属性的,可以和其它字段Alisa.
  • [NativeContainer]不能作为其它[NativeContainer]的子项. 比如: NativeArray<NativeSlice<T>>.

5.1.4 AssumeRange attribute

AssumeRange 属性,表示告诉burst标量的范围,如果burst知道该范围,会进行相关的优化,比如:

[return:AssumeRange(0u, 13u)]
static uint WithConstrainedRange([AssumeRange(0, 26)] int x)
{
    return (uint)x / 2u;
}

有两个限制: 

  • 只可以添加到 (signed or unsigned) 整形上面.
  • range的参数类型,必须和添加属性的类型一致.

Burst 已经对 NativeArray、 NativeSlice 的.Length属性做了替换,因为它永远是正的,比如:

static bool IsLengthNegative(NativeArray<float> na)
{
    // Burst 总是用常量false替换它
    return na.Length < 0;
}

比如:下面表示_length是永远>0的

struct MyContainer
{
    private int _length;

    [return: AssumeRange(0, int.MaxValue)]
    private int LengthGetter()
    {
        return _length;
    }

    public int Length
    {
        get => LengthGetter();
        set => _length = value;
    }

    // Some other data...
}

5.1.5 Hint intrinsics

它告诉Burst优先优化分支内的代码,减少编译时间:

  • Unity.Burst.CompilerServices.Hint.Likely: 很大可能为真
  • Unity.Burst.CompilerServices.Hint.Unlikely: 很大可能为假
  • Unity.Burst.CompilerServices.Hint.Assume: 为真,谨慎使用,相当于宏定义
判断的值还是b,只不过告诉burst,b很大可能为true
if (Unity.Burst.CompilerServices.Hint.Likely(b))
{
    // 这里的任何代码都将被Burst优化
}
else
{
    // 这里的代码不会被优化
}


 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】查看某个进程的tcp全连接队列长度

TCP三次握手成功后,会把连接放到全连接队列里,等待服务器端accept后移除。 如下图所示,图片转自:https://zhuanlan.zhihu.com/p/547279481 下图转自博客:https://zhuanlan.zhihu.com/p/340016138 TCP三次握手过程中,第一次握手server收到client的syn后,内核会把该连接存…

多功能知识付费源码下载-实现流量互导多渠道变现(带详细安装教程)

资源变现类产品的许多优势&#xff0c;并剔除了那些无关紧要的元素&#xff0c;使得本产品在运营和变现能力方面实现了质的飞跃。多领域素材资源知识变现营销裂变独立版本。 支持&#xff1a;视频、音频、图文、文档、会员、社群、用户发布、创作分成、任务裂变、流量主、在线…

缺陷检测项目 | 基于深度学习的钢管焊缝缺陷检测

项目应用场景 面向钢管焊缝缺陷检测场景&#xff0c;使用深度学习算法来实现&#xff0c;提供钢管焊缝缺陷检测数据集&#xff0c;数据集已经标注整理好&#xff0c;包括 YOLO 和 PASCAL VOC 数据格式&#xff0c;项目检出效果好。 训练数据集展示 项目效果 项目细节 > 具体…

oracle19c安装-aarch64

建议 参考oracle官方文档提供的软硬件要求 https://docs.oracle.com/en/database/oracle/oracle-database/19/ladbi/operating-system-checklist-for-oracle-database-installation-on-linux.html#GUID-E5C0A90E-7750-45D9-A8BC-C7319ED934F0 建议使用OracleLinux8.6及以上操作…

一个页面实现两个滚动条【前端】

一个页面实现两个滚动条【前端】 前言版权推荐一个页面实现两个滚动条最后 前言 2024-4-2 12:54:46 以下内容源自《【前端】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://jsss-1.blog.csdn.net …

TIA博途V17开启仿真后软件卡顿的解决办法

TIA博途V17开启仿真后软件卡顿的解决办法 如下图所示,打开TIA博途V17软件,同时打开任务管理器,监控CPU和内存的使用情况,由于我的内存是32G的,而且没有打开其他的任何软件,所以这里可以暂时不考虑内存的影响, 如下图所示,我们在性能中选中CPU,右键选择“将图形更改为—…

电脑常见故障检测方法与对应问题分析说明

电脑常见故障检测方法与对应问题分析说明 前言说明1、机器无法开机故障2、屏幕无法显示3、无法联网4、能开机但是无法进入系统&#xff0c;提示not boot5、USB接口无法识别U盘 前言说明 本文为小白向&#xff0c;许多内容属于经验学而非科学&#xff0c;还望大佬们轻喷。 如上…

快速入门Linux,Linux岗位有哪些?(一)

文章目录 Linux与Linux运维操作系统&#xff1f;操作系统图解 认识LinuxLinux受欢迎的原因什么是Linux运维Linux运维岗位Linux运维岗位职责Linux运维架构师岗位职责Linux运维职业发展路线计算机硬件分类运维人员的三大核心职责 运维人员工作&#xff08;服务器&#xff09;什么…

网页录制视频技巧大揭秘,让你快速成为录制高手

在信息化快速发展的今天&#xff0c;网页录制视频已经成为一种常见的信息获取、传播和保存方式。无论是在线教育、会议记录还是产品展示&#xff0c;视频录制都能以直观生动的方式传达信息。本文将详细介绍三种常见的网页录制视频方法&#xff0c;通过分步骤详细讲解&#xff0…

键盘输入与屏幕输出——getchar()之深入分析

使用getchar&#xff08;&#xff09;输入字符时的怪象 以回车符 \n 结束字符的输入 输入的字符&#xff08;包括回车符&#xff09;都放在输入缓冲区中 怪象背后的原因 行缓冲&#xff08;Line-buffer&#xff09;输入方式 *将输入字符先放入输入缓冲队列中&#xff0c;再…

25.死锁

一个线程如果需要同时获取多把锁&#xff0c;就容易产生死锁。 t1线程获得A对象锁&#xff0c;接下来想获取B对象的锁。 t2线程获得B对象锁&#xff0c;接下来想获取A对象的锁。 /*** 死锁demo* param args*/public static void main(String[] args) {Object a new Object(…

递归遍历目录结构和树状展现

在D盘下创建文件夹“电影”&#xff0c;在文件夹“电影”下创建“华语”、“好莱坞”&#xff0c;在文件夹“华语”下创建文件“人民的名义.mp4”、“天安门传奇.mp4”、“程序员统治世界.mp4”&#xff0c;在文件夹“好莱坞”下创建文件“国王的演讲.mp4”、“速度与激情8.mp4…

QUndoCommand的使用

目录 引言基本实现主要组成命令&#xff08;QUndoCommand&#xff09;命令栈&#xff08;QUndoStack&#xff09; 优化技巧组合命令合并命令 完整代码 引言 实现撤销重做&#xff08;Undo/Redo&#xff09;是编辑器的必备功能&#xff0c;诸如文本编辑器、电子表格、图像编辑器…

Nginx 日志输出配置json格式

nginx日志输出配置json格式 nginx服务器日志相关指令主要有两条&#xff1a; (1) 一条是log_format&#xff0c;用来设置日志格式 (2) 另外一条是access_log&#xff0c;用来指定日志文件的存放路径、格式和缓存大小。 log_format指令用来设置日志的记录格式&#xff0c;它的语…

go入门到精通

初识Go语言 Go语言介绍 Go语言是什么 2009年11月10日&#xff0c;Go语言正式成为开源编程语言家庭的一员。 Go语言&#xff08;或称Golang&#xff09;是云计算时代的C语言。Go语言的诞生是为了让程序员有更高的生产效率&#xff0c;Go语言专门针对多处理器系统应用程序的编…

【Leetcode】2952. 需要添加的硬币的最小数量

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个下标从 0 0 0 开始的整数数组 c o i n s coins coins&#xff0c;表示可用的硬币的面值&#xff0c;以及一个整数 t a r g e t target target 。 如果存在某个 c o i …

SpringCloud学习(1)-consul

consul下载安装及使用 1.consul简介 Consul是一种开源的、分布式的服务发现和配置管理工具&#xff0c;能够帮助开发人员构建和管理现代化的分布式系统。它提供了一套完整的功能&#xff0c;包括服务注册与发现、健康检查、KV存储、多数据中心支持等&#xff0c;可以帮助开发人…

【C语言】InfiniBand内核驱动_mlx4_ib_post_send

一、注释 以下是_mlx4_ib_post_send函数的注释&#xff0c;该函数用于处理InfiniBand工作请求&#xff08;WRs&#xff09;的发送过程&#xff1a; static int _mlx4_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr,const struct ib_send_wr **bad_wr, bool …

备考ICA----Istio实验15---开启 mTLS 自动双向认证实验

备考ICA----Istio实验15—开启mTLS自动双向认证实验 在某些生成环境下,我们希望微服务和微服务之间使用加密通讯方式来确保不被中间人代理. 默认情况下Istio 使用 PERMISSIVE模式配置目标工作负载,PERMISSIVE模式时,服务可以使用明文通讯.为了只允许双向 TLS 流量&#xff0c;…

XGB回归预测

关键代码 import numpy as np import matplotlib.pyplot as plt from xgboost import XGBRegressor #pip install xgboost -i https://pypi.tuna.tsinghua.edu.cn/simple import pandas as pd import joblib#处理中文字体 plt.rcParams[font.family] [sans-serif] plt.rcPar…