17.高并发场景下CAS效率的优化

文章目录

  • 高并发场景下CAS效率的优化
    • 1.空间换时间(LongAdder)
    • 2.对比LongAdder和AtomicLong执行效率
      • 2.1.AtmoictLong
      • 2.2.LongAdder
      • 2.3.比对
    • 3.LongAdder原理
      • 3.1.基类Striped64内部的三个重要成员
      • 3.2.LongAdder.add()方法
      • 3.3.LongAdder中longAccumulate()方法
      • 3.4.LongAdder.casCellsBusy()方法

高并发场景下CAS效率的优化

在高并发情况下,CAS操作的自旋重试会导致系统开销的增加,甚至有些线程可能进入一个无线重复的循环中。

除了存在CAS空自旋外,在SMP机构的CPU平台上,大量的CAS操作还可能导致"总线风暴"

总线风暴是指在计算机系统中,由于大量的处理器或设备同时请求总线而导致的总线利用率骤增的现象。在多处理器系统或者高性能计算机中,当大量的处理器或者设备同时尝试访问系统总线时,可能会出现总线的瓶颈,导致系统性能下降或者崩溃。

总线风暴通常发生在以下情况下:

  1. 大量处理器竞争总线资源: 在多处理器系统中,如果大量的处理器同时竞争总线资源,比如同时发送读写请求或者数据传输请求,就会导致总线的繁忙,进而引发总线风暴。
  2. 外设访问频繁: 除了处理器外,系统中的其他外设,如存储设备、网络接口等,如果频繁地访问总线,也可能造成总线风暴。

总线风暴会带来严重的系统性能问题,包括但不限于:

  • 性能下降: 总线风暴会导致总线资源的过度利用,从而降低了其他设备和处理器对总线的访问效率,系统的整体性能会因此下降。
  • 数据丢失和冲突: 当多个设备同时请求总线时,可能会导致数据的丢失和冲突,影响数据的正确传输和处理。
  • 系统稳定性问题: 如果总线风暴持续时间较长或者严重程度较高,可能会导致系统崩溃或者死锁等严重的稳定性问题。

主要影响的有一下几方面:

  1. 竞争激烈导致自旋时间延长: 当有多个线程同时竞争同一份资源时,只有一个线程能够成功执行CAS操作,其他线程将会不断进行自旋重试。如果竞争激烈,那么自旋时间会变得非常长,因为每个线程都需要等待前一个线程释放资源,才能尝试进行CAS操作。这样一来,自旋的时间延长会增加系统的开销,降低系统的性能。

  2. 上下文切换开销增加: 自旋重试过程中,如果一个线程在自旋过程中被抢占,需要进行上下文切换到其他线程,再切回来时会带来额外的开销。在高并发情况下,频繁的上下文切换会导致系统资源的浪费,降低系统的整体性能。

  3. 缓存竞争增加: 当多个线程同时竞争同一份资源时,由于缓存一致性协议的存在,会导致缓存行的竞争和缓存行的无效化。这会增加缓存访问的延迟和内存总线的压力,进而影响系统的性能。

  4. 对系统负载的影响: 随着自旋重试次数的增加,系统的负载也会增加。这会影响系统的响应速度和吞吐量,导致系统的整体性能下降。

1.空间换时间(LongAdder)

JDK8,提供了一个新的类,LongAdder,以空间换时间的方式提升高并发场景下CAS的操作性能。

LongAdder是Java并发包(java.util.concurrent)中提供的一种用于高并发环境下的原子累加器类型。它主要解决了在高并发情况下使用AtomicLong时可能出现的性能瓶颈问题。核心思想就是:热点分离

LongAdder的主要特点和优势包括:

  1. 分段累加: LongAdder内部采用了分段的方式来进行累加操作。它将累加器的值分成多个段每个段有一个独立的累加器变量,称为"cell"。这样,在高并发情况下,不同线程可以同时对不同段的累加器进行操作,减少了线程竞争,提高了并发性能。(将value值分割成一个数组,当多线程访问时,通过Hash算法将线程映射到一个元素进行操作,从而获取value的结果,最终将数组的元素求和

    1. 在这里插入图片描述
  2. 无锁操作: LongAdder的每个段都是独立的,因此在对各个段进行累加操作时,并不需要加锁。它使用了一种类似于CAS(Compare And Swap)的乐观锁机制,避免了使用全局锁带来的性能损耗。

  3. 高并发性能: 在高并发环境下,LongAdder的性能明显优于AtomicLong。由于采用了分段累加和无锁操作,LongAdder能够更好地应对大量线程同时对累加器进行操作的情况,从而提高了系统的并发性能。

  4. 自动扩容: 如果当前的线程数量超过了初始分段的数量,LongAdder会自动扩容,增加更多的段来适应更大的并发量,从而保持较高的性能。

  5. 统计汇总: LongAdder提供了sum()方法来获取所有段的累加器值的总和,方便对累加结果进行统计和汇总。

2.对比LongAdder和AtomicLong执行效率

下面我们做个小实验,通过代码来观察一下 LongAdder和AtomicLong执行的一个效率,我们使用10个线程,每个线程累计累加1000次管家一下执行时间

2.1.AtmoictLong

 private static final Logger log = LoggerFactory.getLogger(LongAdderTest.class);

    @Test
    @DisplayName("测试AtmoictLong的执行效率")
    public void testLongAdder() {
        long start = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(10);
        AtomicLong longAdder = new AtomicLong();
        ArrayList<Thread> threads = new ArrayList<>();
        for (int j = 0; j <10; j++) {
            threads.add(new Thread(()->{
                for (int i = 0; i < 1000000000; i++) {
                    longAdder.incrementAndGet();
                }
                latch.countDown();
            }));

        }

        // 启动全部线程
        threads.forEach(Thread::start);

        // 等待全部线程执行完毕
        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


        long end = System.currentTimeMillis();

        log.error("执行结果:{}",longAdder.longValue());
        log.error("执行耗时:{}ms",end-start);
    }

在这里插入图片描述

2.2.LongAdder

    @Test
    @DisplayName("测试LongAdder的执行效率")
    public void testLongAdder() {
        long start = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(10);
         LongAdder longAdder = new LongAdder();
        ArrayList<Thread> threads = new ArrayList<>();
        for (int j = 0; j <10; j++) {
            threads.add(new Thread(()->{
                for (int i = 0; i < 1000000000; i++) {
                    longAdder.add(1);
                }
                latch.countDown();
            }));

        }

        // 启动全部线程
        threads.forEach(Thread::start);

        // 等待全部线程执行完毕
        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


        long end = System.currentTimeMillis();

        log.error("执行结果:{}",longAdder.longValue());
        log.error("执行耗时:{}ms",end-start);
    }

在这里插入图片描述

2.3.比对

但是LongAdder 也不是任意场景下都比 Atomic块,如果数量量小的情况下,AtomicLong的速度要更快一些的

AtomicLong 是一种基于 CAS(Compare And Swap)的方式实现的原子操作类,适用于并发量比较小的场景。它在单个变量上执行原子操作,适用于高并发但并发量不是特别大的情况。因为它的实现比较轻量级,适合于竞争激烈但线程数量不是特别多的情况。

LongAdder 则是针对高并发场景做了优化的一种方式。它将变量分散到多个单元中,并行地执行更新操作,然后在需要获取当前值的时候将这些单元的值合并起来。这种方式在高并发的情况下能够减小竞争,从而提高性能。但是在并发量比较小的情况下,这种分散和合并的操作会带来额外的开销,使得 LongAdder 的性能略逊于 AtomicLong。

选择使用 LongAdder 还是 AtomicLong 取决于具体场景和需求。

3.LongAdder原理

AtomicLong使用内部变量value保存的实际的long值,所有的操作都是针对该value变量进行,在高并发的环境下面,value变量其实就是一个热点,也就是N个线程竞争一个热点。重新线程越多,也就意味着CAS失败的概率越高,从而进入恶性的CAS自旋状态

LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中不同槽中的元素,每个线程只能对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多了

LongAdder的实现思路其实和CurrentHashMap中分段使用的原理非常相似,本质上都是不同的线程,在不同的单元上进行操作,减少了线程的竞争,提高了并发的效率。

LongAdder的内部成员包含一个base值 和 一个cells数组,在最初无竞争的时候,只能操作base的值。只有当线程执行CAS失败后,才会初始cells数组,并为线程分配所对应的元素。

LongAdder的设计目的是为了在高并发情况下提供更好的性能,相比于传统的原子操作类(比如 AtomicLong),在高并发下可以减少竞争。

  1. LongAdder 的核心思想是将一个 long 类型的值分解成多个单元(cell),每个单元都是独立的,每个单元保存一部分的累加值。
  2. 当多个线程同时对 LongAdder 进行累加操作时,LongAdder 会根据线程的哈希值选择不同的单元进行累加操作,这样可以减小竞争。如果发生了哈希冲突,会采用 CAS 操作尝试更新单元的值。
  3. 当需要获取当前值时,LongAdder 会将所有单元的值累加起来,得到最终的结果。由于每个单元都是独立的,所以在获取值的时候不需要加锁,可以并行地进行。这样就减小了获取值时的开销,提高了性能。

核心优势

  • 减小竞争:通过分段累加和并行更新,减小了多线程下的竞争,提高了性能。
  • 并行计算:在获取值时,可以并行地将所有单元的值累加起来,减小了获取值时的开销。

总的来说,LongAdder 的设计思路清晰,将累加操作分解成多个独立的单元,通过分段累加和并行计算提高了在高并发情况下的性能表现。

3.1.基类Striped64内部的三个重要成员

LongAdder继承 Striped64类,base值和cells数组都在Striped64类中定义,其中比较重要的几个成员有

/**
  * Table of cells. When non-null, size is a power of 2.
  */
transient volatile Cell[] cells;

/**
  * Base value, used mainly when there is no contention, but also as
  * a fallback during table initialization races. Updated via CAS.
  */
transient volatile long base;

/**
  * Spinlock (locked via CAS) used when resizing and/or creating Cells.
  */
transient volatile int cellsBusy;

1. cells

  • 描述cells 是一个 volatile Cell[] 类型的数组,用于存储多个单元(Cell)。存放cell的哈希表,大小为2的幂
  • 作用:每个单元(Cell)都包含一个 volatile long 类型的变量,用于存储一部分累加值。cells 数组的每个元素都可以被多个线程同时访问和修改,用于存储累加值的其他部分。

2. base

  • 描述base 是一个 volatile long 类型的变量,用于存储一部分累加值。
  • 作用base 可以被多个线程同时访问和修改,用于存储累加值的一部分。

3. cellsBusy

  • 描述cellsBusy 是一个 volatile int 类型的变量,用作自旋锁。
  • 作用:在进行数组扩容或者创建新的 Cell 单元时,通过 CAS 操作来获取锁,以保证只有一个线程进行扩容或者创建操作。

Striped64的整体值value获取如下

// LongAdder中 
/**
 * 获取当前累加器的值(long类型)。
 * @return 当前累加器的值
 */
public long longValue() {
    return sum();
}

/**
 * 计算累加器中所有单元值的总和。
 *
 * @return 累加器中所有单元值的总和
 */
public long sum() {
    // 获取单元数组的引用
    Cell[] cs = cells;
    // 初始化总和为基本值
    long sum = base;
    // 如果单元数组不为null,则遍历每个单元并累加值到总和
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum; // 返回总和
}
  • sum() 方法首先获取了当前累加器中的单元数组 cells 的引用,并将基本值 base 赋值给 sum 变量,作为初始总和。
  • 然后,如果单元数组 cells 不为 null,就遍历每个单元,将单元中的值累加到总和 sum 中。
  • 最后返回总和 sum,即为累加器中所有单元值的总和。

3.2.LongAdder.add()方法

作为示例这里分析一下LongAdder的add方法

/**
 * 将给定的值添加到累加器中。
 *
 * @param x 要添加的值
 */
public void add(long x) {
    Cell[] cs; // 单元数组
    long b, v; // 基本值和单元值
    int m; // 单元数组长度减一
    Cell c; // 单元对象
    // 如果单元数组不为null,或者通过CAS操作将基本值更新为原基本值加上x成功
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        // 获取当前线程的哈希值
        int index = getProbe();
        // 默认情况下,当前线程没有争用(即不与其他线程竞争)
        boolean uncontended = true;
        // 如果单元数组为空,或者单元数组长度减一小于0,或者单元数组中的单元为空,或者单元中的值通过CAS操作更新失败
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[index & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            // 调用longAccumulate方法进行累加操作
            longAccumulate(x, null, uncontended, index);
    }
}

/**
 * 通过CAS操作来更新基本值。
 *
 * @param cmp 期望值
 * @param val 新值
 * @return 如果更新成功返回true,否则返回false
 */
final boolean casBase(long cmp, long val) {
    // 调用 BASE 中的 weakCompareAndSetRelease 方法,尝试使用 CAS 操作更新基本值
    return BASE.weakCompareAndSetRelease(this, cmp, val);
}

  • add 方法首先尝试通过 CAS 操作将给定的值累加到基本值 base 上。如果成功,方法结束。
  • 如果 CAS 操作失败,说明存在竞争或者 cells 数组不为 null,则获取当前线程的哈希值,并尝试更新对应的单元。
  • 如果更新单元的过程中发生竞争或者单元数组为空,则调用 longAccumulate 方法进行累加操作。
  • 这种设计能够有效地减少竞争,提高并发性能,尤其在高并发情况下,避免了多个线程同时更新同一个单元造成的性能下降。

3.3.LongAdder中longAccumulate()方法

longAccumulate()方法是Striped64中重要的方法,主要是实现不同线程更新各自Cell中的值,其逻辑类似于分段式锁。

/**
 * 通过 CAS 操作来累加给定的值。
 *
 * @param x              要累加的值
 * @param fn             用于累加的操作函数
 * @param wasUncontended 标志位,表示当前线程是否为无竞争状态
 * @param index          当前线程的哈希码
 */
final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended, int index) {
    // 如果哈希码为0,则重新计算哈希码并标记为无竞争状态
    if (index == 0) {
        ThreadLocalRandom.current(); // 强制初始化随机数生成器
        index = getProbe(); // 获取当前线程的哈希码
        wasUncontended = true; // 标记为无竞争状态
    }

    // 循环尝试进行累加操作
    for (boolean collide = false;;) { // 标志位,表示上一次操作是否发生哈希冲突
        Cell[] cs; // 单元数组
        Cell c; // 单元对象
        int n; // 单元数组长度
        long v; // 单元值

        // 如果单元数组不为null且长度大于0
        if ((cs = cells) != null && (n = cs.length) > 0) {
            // 计算单元数组中的索引
            if ((c = cs[(n - 1) & index]) == null) {
                // 如果对应的单元为空
                if (cellsBusy == 0) { // 尝试创建新的单元
                    Cell r = new Cell(x); // 乐观地创建单元
                    if (cellsBusy == 0 && casCellsBusy()) { // 尝试获取单元数组更新锁
                        try { // 在锁定状态下重新检查
                            Cell[] rs; // 重新检查后的单元数组
                            int m, j; // 单元数组长度和索引
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & index] == null) {
                                rs[j] = r; // 更新单元数组
                                break; // 跳出循环
                            }
                        } finally {
                            cellsBusy = 0; // 释放单元数组更新锁
                        }
                        continue; // 重新尝试操作
                    }
                }
                collide = false; // 未发生哈希冲突
            }
            else if (!wasUncontended) // 如果已知CAS操作失败
                wasUncontended = true; // 继续哈希冲突后的操作
            else if (c.cas(v = c.value,
                           (fn == null) ? v + x : fn.applyAsLong(v, x)))
                break; // 累加操作成功,跳出循环
            else if (n >= NCPU || cells != cs)
                collide = false; // 达到最大大小或者单元数组失效
            else if (!collide)
                collide = true; // 发生哈希冲突
            else if (cellsBusy == 0 && casCellsBusy()) { // 尝试获取单元数组更新锁
                try { // 在锁定状态下重新检查
                    if (cells == cs) // 如果单元数组未被其他线程修改
                        cells = Arrays.copyOf(cs, n << 1); // 扩容单元数组
                } finally {
                    cellsBusy = 0; // 释放单元数组更新锁
                }
                collide = false; // 未发生哈希冲突
                continue; // 重新尝试操作
            }
            index = advanceProbe(index); // 更新哈希码
        }
        else if (cellsBusy == 0 && cells == cs && casCellsBusy()) { // 尝试获取单元数组更新锁
            try { // 在锁定状态下初始化单元数组
                if (cells == cs) { // 双重检查
                    Cell[] rs = new Cell[2]; // 创建新的单元数组
                    rs[index & 1] = new Cell(x); // 初始化单元
                    cells = rs; // 更新单元数组
                    break; // 跳出循环
                }
            } finally {
                cellsBusy = 0; // 释放单元数组更新锁
            }
        }
        else if (casBase(v = base,
                         (fn == null) ? v + x : fn.applyAsLong(v, x))) // 使用基本值进行累加操作
            break; // 累加操作成功,跳出循环
    }
}

这里主要分为 五个个部分,四个判断(其实主要就是后三个判断),一个自旋更新

  1. 第一个 if 分支
    • 当前线程的哈希码为0时,重新计算哈希码并标记为无竞争状态。
    • 通过 getProbe() 方法获取当前线程的哈希码。
    • 如果当前线程的哈希码为0,强制初始化随机数生成器,然后重新获取哈希码,并标记为无竞争状态。
  2. 循环部分
    • 这是一个无限循环,直到累加操作成功并跳出循环。
    • 有一个 collide 变量用于标记上一次操作是否发生了哈希冲突。
  3. 第二个 if 分支
    • 如果 cells 数组不为 null 且长度大于 0。
    • 获取 cells 数组中对应的单元。
    • 如果单元为空,则尝试创建新的单元,如果成功则更新单元数组并跳出循环,否则继续尝试操作。
    • 如果单元不为空,则尝试进行 CAS 操作更新单元的值,如果成功则累加操作完成并跳出循环,否则继续尝试操作。
    • 如果单元数组达到了最大大小或者单元数组失效,则标记为未发生哈希冲突。
    • 如果发生了哈希冲突且没有其他线程在扩容单元数组,则尝试扩容单元数组并继续操作。
  4. 第三个 if 分支
    • 如果 cells 数组为 null 且没有其他线程在初始化单元数组,则尝试初始化单元数组。
    • 如果成功初始化单元数组,则在其中创建一个新的单元并跳出循环。
  5. 最后一个 else 分支
    • 如果无法避免竞争,则使用基本值进行累加操作。
    • 如果成功,则累加操作完成并跳出循环。

3.4.LongAdder.casCellsBusy()方法

casCellsBusy 方法是 LongAdder 类中的一个私有方法,用于通过 CAS(比较并交换)操作尝试获取单元数组更新锁。当 cellsBusy 的值为0时,表示锁是空闲的,此时该方法会尝试将 cellsBusy 的值从0更新为1,即获取锁。如果成功获取锁,则表示当前线程获得了单元数组的更新权限,可以进行单元数组的更新操作;如果获取锁失败,说明有其他线程已经持有了更新锁,当前线程需要等待。

初始时,LongAdder 中的 cells 数组为 null,此时 casCellsBusy 的主要作用是在第一个线程进行累加操作时,尝试初始化单元数组并获取更新锁。如果当前没有其他线程持有更新锁,那么第一个线程通过 casCellsBusy 方法可以成功获取更新锁,并在更新锁的保护下进行单元数组的初始化。如果有其他线程在尝试初始化单元数组,那么第一个线程就需要等待,直到其他线程释放了更新锁。

/**
 * 通过 CAS 操作来尝试获取单元数组更新锁。
 *
 * @return 如果成功获取锁则返回 true,否则返回 false
 */
final boolean casCellsBusy() {
    // 使用 CELLSBUSY 的 compareAndSet 方法尝试将单元数组更新锁从0更新为1
    return CELLSBUSY.compareAndSet(this, 0, 1);
}

注意

cellsBusy 成员值为1时,表示 cells 数组正在被某个线程执行初始化或者扩容操作。在这种情况下,其他线程不能立即进行如下操作:

  1. 无法更新单元数组的值:由于可能存在单元数组的结构变化(例如初始化或者扩容),其他线程不能直接更新单元数组的值。因为这样做可能会导致并发冲突或者数据丢失。
  2. 无法获取单元数组的更新锁casCellsBusy 方法会失败,因为单元数组的更新锁已经被其他线程持有。这意味着其他线程不能立即获取到单元数组的更新权限,需要等待当前的初始化或者扩容操作完成后才能尝试获取锁并执行更新操作。
  3. 无法创建新的单元:当某个线程尝试创建新的单元时,如果单元数组的某个位置为 null,那么这个线程也不能立即将新的单元放置到该位置。因为该位置可能在被其他线程进行初始化或者扩容操作,直接放置新的单元可能导致数据丢失或者覆盖其他线程的操作。

因此,在 cellsBusy 成员值为1时,其他线程必须等待当前的初始化或者扩容操作完成后,才能安全地进行对 cells 数组的操作,以确保线程安全和数据一致性。

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

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

相关文章

【网络安全】【Frida实战案例】某图xx付费功能逆向分析(一)

文章目录 一、目标应用二、环境三、步骤1、查看布局id2、用到的Log日志类信息3、尝试hook VIP判断方法 四、总结五、相关源码 1、【网络安全】【Frida实践案例】某图xx付费功能逆向分析&#xff08;一&#xff09; 2、【网络安全】【Frida实践案例】某图xx付费功能逆向分析&…

MySQL基础--SQL优化

插入数据 insert 优化 批量插入 手动提交事务 主键顺序插入 大批量插入数据 如果一次性需要大批量插入数据&#xff0c;使用 insert 语句插入性能较低&#xff0c;此时可以使用 MySQL 数据库提供的 load 指令插入&#xff0c;操作如下&#xff1a; 主键优化 在 InnoDB 存储引擎…

QT:QML与C++交互

目录 一.介绍 二.pro文件添加模块 三.h文件 四.cpp文件 五.注册 六.调用 七.展示效果 八.代码 1.qmlandc.h 2.qmlandc.cpp 3.main.cpp 4.qml 一.介绍 在 Qt 中&#xff0c;QML 与 C 交互是非常重要的&#xff0c;因为它允许开发人员充分利用 QML 和 C 各自的优势&…

软考--试题六--策略模式(Strategy)

策略模式(strategy) 意图 定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使它们可以相互替换。此模式使得算法可以独立于使用它们的客户而变化 结构 适用性 1、许多相关的类仅仅是行为有异。“策略”提供了一种多个行为中的一个行为来配置一个类的方法…

虚拟化技术 使用vSphere Web Client管理ESXi主机

一、实验内容 通过vSphere Web Client将ESXi主机连接到iSCSI共享存储通过vSphere Web Client&#xff0c;使用共享存储创建虚拟机并安装windows 2008 R2操作系统通过vSphere Web Client&#xff0c;为虚拟机创建快照 二、、实验主要仪器设备及材料 安装有64位Windows操作系统…

SMB攻击利用之-mimikatz上传/下载流量数据包逆向分析

SMB协议作为windows环境下最为常见的一种协议,在历史上出现过无数的通过SMB协议进行网络攻击利用的案例,包括针对SMB协议本身以及通过SMB协议实施网络攻击。 本文将介绍一种通过SMB协议的常见利用方式,即向远程主机传输mimikatz,作为我的专栏《SMB攻击流量数据包分析》中的…

FPGA - GTX收发器-K码 以及 IBERT IP核使用

一&#xff0c;前言 在FPGA - Xilinx系列高速收发器---GTX中详细介绍了GTX的基础知识&#xff0c;以及IP核的调用&#xff0c;下面将补充一下GTX在使用中的高速串行数据流在接收和发送时的控制与对齐&#xff08;K码&#xff09;&#xff0c;以及高速接口GTX&#xff0c;如果G…

ApiHug - 闭门造车, 出门合辙

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace The Nex…

第18节 神级开源shellcode工具:donut

我做了一个关于shellcode入门和开发的专题&#x1f469;&#x1f3fb;‍&#x1f4bb;&#xff0c;主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料&#xff0c;内容里面的每一个环境我都亲自测试实操过的记录&#xff0c;有需要的小伙伴可以参考。 我的…

python批量生成25位数字字母混合序列号(SN码)

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.代码 三.使用 四.分析 一.前言 SN码,即Serial Number的缩写,有时也被称为Serial No,是产品序列号的意思。它是一个独特的标识符,用于区分同一种类

家用充电桩远程监控安全管理系统解决方案

家用充电桩远程监控安全管理系统解决方案 在当今电动汽车日益普及的背景下&#xff0c;家用充电桩的安全管理成为了广大车主关注的重点问题。为了实现对充电桩的高效、精准、远程监控&#xff0c;一套完善的家用充电桩远程监控安全管理系统解决方案应运而生。本方案旨在通过先…

SAP_ABAP-思考篇

作为一个SAP十年左右的从业者&#xff0c;其实我很清楚&#xff0c;我自身的能力&#xff0c;确实是很多东西都会一点&#xff0c;但是没有一样是精通的。坦白来说&#xff0c;我的个人简介里&#xff0c;虽然也不算夸大&#xff0c;但我估计有些新手小白看着可能会觉得还挺厉害…

KNN算法项目实战之酒的分类

加载数据集 from sklearn.datasets import load_winewine_dataset load_wine()数据集有什么&#xff1f; data&#xff1a;数据 target&#xff1a;目标分类 target_names&#xff1a;目标分类名称 DESCR&#xff1a;数据描述 features_names&#xff1a;特征变量名称 查…

在k8s中搭建elasticsearch高可用集群,并对数据进行持久化存储

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《洞察之眼&#xff1a;ELK监控与可视化》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Elasticsearch简介 2、k8s简介 二、环境准备 …

27- ESP32-S3 USB虚拟串口(USB-OTG 外设介绍)

ESP32-S3 USB虚拟串口详解 USB-OTG 外设介绍 USB-OTG&#xff1a; USB-OTG是一种USB规范&#xff0c;允许嵌入式系统&#xff08;如手机、平板电脑、单片机系统等&#xff09;在没有主机&#xff08;如个人电脑&#xff09;的情况下直接相互通信&#xff0c;同时也能够作为传…

PG Vacuum 空间管理工具与pg_freespacemap使用

1.什么是Vacuum&#xff1a; PG pageinspect使用与块空间清理学习-CSDN博客 之前说过PG块更新的特点:先删除后插入&#xff0c;但老元组并不会真正删除&#xff0c;只是把老元组标记为删除状态&#xff0c;这就导致了PG中会有大量的垃圾数据&#xff0c;update所造成的资源消…

train_gpt2_fp32.cu - main

llm.c/test_gpt2_fp32.cu at master karpathy/llm.c (github.com) 源码 // ---------------------------------------------------------------------------- // main training loop int main(int argc, char *argv[]) {// read in the (optional) command line argumentsco…

API数据对接:本地缓存与日志记录的重要性

关键词&#xff1a;数据治理项目、API接口、数据中心、第三方系统、数据异常、本地缓存、日志记录、数据整合、多源异构数据、数据处理效率 阅读建议&#xff1a; 对于数据治理、API接口和系统集成领域的专业人士&#xff0c;本文深入剖析了本地缓存和日志记录在确保系统稳定性…

ArcGI基本技巧-科研常用OLS, GWR, GTWR模型实现

ArcGI基本技巧-科研常用OLS, GWR, GTWR模型实现 OLS,GWR,GTWR回归模型均可以揭示解释变量对被解释变量的影响且可以进行预测。Ordinary Least Squares (OLS)是最小二乘法&#xff0c;Geographically Weighted Regression (GWR)是地理加权回归&#xff0c;Geographically and T…

pytorch-8 单层神经网络及激活函数

一、单层回归网络:线性回归 1. tensor手动实现单层回归神经网络的正向传播 # tensor手动实现单层回归神经网络的正向传播 import torch from torch.nn import functional as FX = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32) # 特征张量 w =…