底层知识总结

底层开发常用知识点积累 这篇博客主要是积累一下平时开发中有用的一些小的知识点。 printk打印 #define AUDIO_LOGI(fmt,...) printk("[AUDIO][%s][%d][INFO]"fmt, __func__, __LINE__, ##__VA_ARGS__) #define AUDIO_LOGW(fmt,...) printk("[AUDIO][%s][%d][WARNING]"fmt, __func__, __LINE__, ##__VA_ARGS__) #define AUDIO_LOGE(f 阅读详情

超线程:一个ALU对应多个PC | Registers

所谓的四核八线程

ALU -> Arithmetic & Logic Unit 运算单元

 存储器的层次结构

为什么存在缓存:CPU的速度太快,内存速度太慢

越靠近 CPU 的缓存越快也越小。

所以 L1 缓存很小但很快,并且紧靠着在使用它的 CPU 内核。

L2 大一些,也慢一些,并且仍然只能被一个单独的 CPU 核使用。

L3 在现代多核机器中更普遍,仍然更大,更慢,并且被单个插槽上的所有 CPU 核共享。

最后,主存保存着程序运行的所有数据,它更大,更慢,由全部插槽上的所有 CPU 核共享。

当 CPU 执行运算的时候,它先去 L1 查找所需的数据,再去 L2,然后是 L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。

走得越远,运算耗费的时间就越长。

所以如果进行一些很频繁的运算,要确保数据在 L1 缓存中。

按块读取

读取数据时,由于局部性原理,将相邻的数据一块读取。可以提高效率。(不管是硬盘往内存读或者内存往缓存读)

缓存行Cache Line:

每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。

缓存行越大,局部性空间效率越高,但读取时间慢

缓存行越小,局部性空间效率越低,但读取时间快

取一个折中值,最常见的缓存行大小是64个字节。

多个CPU时,需要保持数据的一致性,这里用的是一致性协议

MESI Cache一致性协议

伪共享:

当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

在程序运行的过程中,缓存每次更新都从主内存中加载连续的 64 个字节。因此,如果访问一个 long 类型的数组时,当数组中的一个值被加载到缓存中时,另外 7 个元素也会被加载到缓存中。

但是,如果使用的数据结构中的项在内存中不是彼此相邻的,比如链表,那么将得不到免费缓存加载带来的好处。

不过,这种免费加载也有一个坏处。设想如果我们有个 long 类型的变量 a,它不是数组的一部分,而是一个单独的变量,并且还有另外一个 long 类型的变量 b 紧挨着它,那么当加载 a 的时候将免费加载 b。

看起来似乎没有什么毛病,但是如果一个 CPU 核心的线程在对 a 进行修改,另一个 CPU 核心的线程却在对 b 进行读取。

当前者修改 a 时,会把 a 和 b 同时加载到前者核心的缓存行中,更新完 a 后其它所有包含 a 的缓存行都将失效,因为其它缓存中的 a 不是最新值了。

而当后者读取 b 时,发现这个缓存行已经失效了,需要从主内存中重新加载。

请记住,我们的缓存都是以缓存行作为一个单位来处理的,所以失效 a 的缓存的同时,也会把 b 失效,反之亦然。

伪共享例子:

package c_020;

public class FalseSharing {
    public static void main(String[] args) throws InterruptedException {
        testPointer(new Pointer());
    }

    private static void testPointer(Pointer pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }
}
class Pointer {
    volatile long x;
    volatile long y;
}

这个例子中,我们声明了一个 Pointer 的类,它包含 x 和 y 两个变量(必须声明为volatile,保证可见性,关于内存屏障的东西我们后面再讲),一个线程对 x 进行自增1亿次,一个线程对 y 进行自增1亿次。

可以看到,x 和 y 完全没有任何关系,但是更新 x 的时候会把其它包含 x 的缓存行失效,同时也就失效了 y,运行这段程序输出的时间为3258ms。

避免伪共享

伪共享的原理我们知道了,一个缓存行是 64 个字节,一个 long 类型是 8 个字节,所以避免伪共享也很简单,大概有以下三种方式:

(1)在两个 long 类型的变量之间再加 7 个 long 类型

package c_020;

public class FalseSharing2 {
    public static void main(String[] args) throws InterruptedException {
        testPointer(new Pointer2());
    }

    private static void testPointer(Pointer2 pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }
}
class Pointer2 {
    volatile long x;
    long p1, p2, p3, p4, p5, p6, p7;
    volatile long y;
}

再次运行程序,会发现输出时间神奇的缩短为了499ms。

(2)重新创建自己的 long 类型,而不是 java 自带的 long

class Pointer {
    MyLong x = new MyLong();
    MyLong y = new MyLong();
}

class MyLong {
    volatile long value;
    long p1, p2, p3, p4, p5, p6, p7;
}

这种个人感觉和第一种一样,就不演示了

(3)使用 @sun.misc.Contended 注解(java8)

修改 MyLong 如下:

@sun.misc.Contended
class MyLong {
    volatile long value;
}
package c_020;

public class FalseSharing3 {
    public static void main(String[] args) throws InterruptedException {
        testPointer(new Point3());
    }

    private static void testPointer(Point3 pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.x.value++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.y.value++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }
}
class Point3{
    MyLong x = new MyLong();
    MyLong y = new MyLong();
}
@sun.misc.Contended
class MyLong {
    volatile long value;
}

或者:

package c_020;

import sun.misc.Contended;

public class FalseSharing {
    public static void main(String[] args) throws InterruptedException {
        testPointer(new Pointer());
    }

    private static void testPointer(Pointer pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }
}
class Pointer {
    @Contended
    volatile long x;
    @Contended
    volatile long y;
}

默认使用这个注解是无效的,需要在JVM启动参数加上-XX:-RestrictContended才会生效,再次运行程序发现时间是634ms。

注意,以上三种方式中的前两种是通过加字段的形式实现的,加的字段又没有地方使用,可能会被jvm优化掉,所以建议使用第三种方式。

缓存行对齐:

对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,可以使用缓存航对齐的编程方式,上面避免非共享的方式,就是缓存行对齐。

CPU的乱序执行

乱序证明:如果没有乱序,那么绝对不会出现x=0,y=0的情况

public class T04_Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b =0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                public void run() {
                    //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
                    //shortWait(100000);
                    a = 1;
                    x = b;
                }
            });

            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            one.start();other.start();
            one.join();other.join();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                //System.out.println(result);
            }
        }
    }


    public static void shortWait(long interval){
        long start = System.nanoTime();
        long end;
        do{
            end = System.nanoTime();
        }while(start + interval >= end);
    }
}

禁止指令重排序:

CPU层面如何禁止重排序

使用:内存屏障

原理:对某部分内存做操作时前后添加的屏障,屏障前后的操作不可以乱序执行

Intel中的屏障为:

lfence  mfence sfence原语,当然也可以使用总线锁来解决

sfence(Store Barrier):在sfence指令前的写操作必须在sfence指令后的写操作前完成。 
lfence(Load Barrier):在lfence指令前的读操作必须在lfence指令后的读操作前完成。 
mfence(是一种全能型的屏障,具备ifence和sfence的能力):在mfence指令前的读写操作必须在mfence指令后的读写操作前完成。

Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。

Lock前缀实现了类似的能力:

1. 它先对总线/缓存加锁,然后执行后面的指令,最后释放锁后会把高速缓存中的脏数据全部刷新回主内存。

2. 在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。Lock后的写操作会让其他CPU相关的cache line失效,从而从新从内存加载最新的数据。这个是通过缓存一致性协议做的。

 

JSR内存屏障(JVM层级的)

LoadLoad屏障:

        对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:

        对于这样的语句Strore1;StoreStore;Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见。

LoadStore屏障:

        对于这样的语句Load1;LoadStore;Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:

        对于这样的语句Store1;StoreLoad;Load2;,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

注意:volatile底层(CPU层面)使用的是lock指令

as if serial

不管如何重排序,单线程执行结果不会改变。

合并写:(不重要)

Write Combining Buffer

一般是4个字节

由于ALU速度太快,所以在写入L1的的同时,写入一个WC Buffer,满了之后,再直接更新到L2。

证明:

public final class WriteCombining {

    private static final int ITERATIONS = Integer.MAX_VALUE;
    private static final int ITEMS = 1 << 24;
    private static final int MASK = ITEMS - 1;

    private static final byte[] arrayA = new byte[ITEMS];
    private static final byte[] arrayB = new byte[ITEMS];
    private static final byte[] arrayC = new byte[ITEMS];
    private static final byte[] arrayD = new byte[ITEMS];
    private static final byte[] arrayE = new byte[ITEMS];
    private static final byte[] arrayF = new byte[ITEMS];

    public static void main(final String[] args) {

        for (int i = 1; i <= 3; i++) {
            System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
            System.out.println(i + " SplitLoop  duration (ns) = " + runCaseTwo());
        }
    }

    public static long runCaseOne() {
        long start = System.nanoTime();
        int i = ITERATIONS;

        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }

    public static long runCaseTwo() {
        long start = System.nanoTime();
        int i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
        }
        i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }
}

NUMA---Non uniform Memory Access

先看UMA

ZGC-NUMA aware

 

 

 

 

 

 

 

 

php实现概率性随机抽奖代码 1、初始数据: 权重越大,抽取的几率越高 [奖品1, 权重 5], [ 奖品2, 权重6], [ 奖品3, 权重 7], [ 奖品4, 权重2] 2、处理步骤: 1)N = 5 + 6 + 7 + 2 = 20 2)然后取1-N的随机数M 3)界定各 奖品的权重范围值 奖品 1 : 1-5 ; 奖品2 : 6-11; 奖品3: 12-18; 奖品4: 19-20 4) 如果M在某个奖品的权重范围值内,标识这个奖品被抽取到 <?php /** * 奖品 */ class Prize { # ID public $id = null; # 权重 public $weigh 立即下载

相关推荐

Python 高级语法:闭包

闭包(Closure)是函数式编程中的一个重要概念。在Python中,闭包可以简单理解为:一种特殊的函数。外函数:嵌套函数中最外层的函数称之为外函数。内函数:嵌套函数中最内层的函数称之为内函数;内函数引用了外函数的临时变量;外函数的返回使用了临时变量的内函数。

moment159的博客 2354

4.l2cache 关于缓存的几个常见问题分析和处理方案

l2cache 分布式二级缓存组件 l2cache 是一个基于 Caffeine、 Redis 、 Spring Cache 实现的满足高并发场景下的分布式二级缓存解决方案。 Caffeine :一级缓存 L1,也就是内存缓存,而内存天然支撑高并发的。 Redis : 二级缓存 L2,也就是集中式缓存。 注意: 1、由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 2、L2 可以避免应用重启后导致的 L1 数据丢失的问题

白云 5900

AIDD-人工智能药物设计-构建基于机器学习方法的在线预测平台高准确预测药物诱导骨毒性

在此基础上,提取多种药物分子特征,包括分子指纹(如MACCS、Morgan、RDKit、TopologicalTorsion、AtomPairs与Descriptors)及分子图谱,通过Transformer编码器与神经网络进行机器学习模型训练。最后,将训练好的模型部署为在线Web服务器(BoneToxPD),供用户快速预测药物的骨毒性。本研究不仅在骨毒性预测的准确性方面取得了突破,还在模型解释性方面提供了新的视角,通过注意力机制精准识别出影响骨毒性的关键化学基团,推动药物研发领域更安全、更高效的发展。

itwangyang520的博客 1080

解决 vue 多层级路由 缓存失效 解决基于 keep-alive 的多级路由缓存问题 vue keep-alive 缓存失效 vue-element-admin多层级路由 缓存失效

首先keep-alive是在Layout上进行处理,如果不缓存Empty,则Empty下面的页面将无法被缓存,如果缓存Empty,又会导致Empty里面的所有页面都被缓存,无法按需清除,相信接触过的肯定感同身受其中的大坑。这里需要注意,路由配置还是保持多级嵌套的形式,而这个配置并非最终注册使用的路由,仅仅是提供侧边栏导航菜单使用,同时再生成一份用于动态注册路由的数据,图例如果没看明白的话,可以看下面两组数据。进行显示了,所以在处理路由数据的同时,也需要处理面包屑导航的信息。...

半只 4443

【网络】Ikuai中如何添加/更换虚拟机(图文)

Ikuai 添加虚拟机教程

qq_20623849的博客 6536

Pytorch实现softmax多分类

PyTorch实现softmax多分类

天涯雨的博客 1388

Redis+Caffeine两级缓存,让访问速度纵享丝滑

在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在提升访问速度的同时,也能降低数据库的压力。 随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与服务性能。于是,就产生了使用本地缓存作为一级缓存,再加上远程缓存作为二...

uuqaz的博客 1308

音视频入门:H264、H265概念总结

添加链接描述 什么是H.264与H.265 H.264与H.265都是视频压缩格式,由于视频本身的码流太大,所以需要经过压缩然后再通过网络进行传输。H.264是目前比较主流的压缩算法,像视频会议设备一般都采用这个编码格式。基础的H.264可以支持在1M带宽下传输720P30帧/秒的图像;H.264 HIGH PROFILE支持在512K带宽下传输720P30帧/秒的图像。 H.265是比较新的压缩算法,可以更一步提高压缩比,随着我们现在生活中出现的视频格式越来越大(比如现在基本都是1080P甚至4K的显示器

weixin_40673765的博客 8937

操作系统——底层结构与设计原理知识汇总

JOS源文件安排: inc/ 这个文件夹是一些include文件,即各种类型的头文件。 env.h Public definitions for user-mode environments trap.h Public definitions for trap handling sy...

qq_41230365的博客 1136

CodeBuddy 接入 MCP,一键生成网站!

2024 年 11 月,Anthropic 公司搞了个挺有意思的新玩意 - Model Context Protocol(模型上下文协议)简称为 MCP 协议。简单来说,它就是给 AI 和各类工具数据之间搭了个标准化的”桥梁”,让开发者不用再为对接问题头疼了。mcp.so如下图所示,这是 mcp.so 网站中的 MCP Server。使用百度/高德地图分析旅线计算时间接 Puppeteer 自动操作网页使用 Github/Gitlab 让大模型接管代码仓库。

悟空聊架构的专栏 1946

基于Simulink的无线充电系统LCC补偿网络建模与控制

本文详细介绍了基于Simulink的无线充电系统LCC补偿网络建模与控制方法。首先分析了LCC补偿网络的恒流输出、软开关实现和抗偏移能力等优势,并对比了不同补偿拓扑特性。随后分步骤讲解了Simulink建模流程,包括LCC主电路构建、关键参数设计、双闭环控制系统实现等核心环节。重点阐述了移相控制策略和软开关验证方法,提供了系统效率计算和抗偏移测试的调试技巧。最后展示了仿真结果,在耦合系数变化20%时,闭环系统仍能保持输出电压稳定在±2%波动范围内。文章还探讨了工程扩展方向,并针对常见问题给出了解决方案,为无

xiaoheshang_123的博客 539

Mybatis的二级缓存问题

一、一级缓存、二级缓存的定义及区别 我们知道mybatis的对JDBC 的一个轻量级的封装,在运行速度方面优于hibernate。 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

y2017的博客 2万+

优快云独家 | 全网首发 | 《目标检测蓝皮书》目录

10篇文章从目标检测小白变大神

Coding... 3万+

底层知识学习记录目录表

本文是将前面学习的底层知识的博客总结成一个目录表,方便以后个人的复习 欢迎共同学习交流 【软件开发底层知识修炼】一 深入浅出处理器之一 微处理器与微控制器 【软件开发底层知识修炼】二 深入浅出处理器之二 中断的概念与意义 【软件开发底层知识修炼】三 深入浅出处理器之三 内存管理与内存管理单元(MMU) 【软件开发底层知识修炼】四 深入浅出处理器之四 结合高速缓存以及TLB与虚拟内存 【软...

厚积薄发 1万+

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

程序员是否需要学习底层知识

程序员是否需要学习底层知识? 这两天每天中午午休前都会看一些《编程人生》。现在已经看了七八个人,这些人开始编程的时候,正好是计算机的起步期,所以对整个计算机的底层都比较了解。不知道这是不是这些人能够成为大师的一个必要条件,自己有没有必要在这方面努力一下。我想这也是很多程序员思考过的问题。现在分享一下我的思考,和大家一起探讨。——ps:这些人大部分在中学的时候就开始编程,而那个时候我们一般都正在为...

weixin_30952535的博客 300

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Java底层知识面试题

主题 链接 Java基础知识 面试题 Java集合框架 面试题 Java并发编程 面试题 Redis 面试题 文章目录JVM内存结构class文件格式运行时数据区:堆、栈、方法区、直接内存、运行时常量池堆和栈区别Java中的对象一定在堆上分配吗?Java内存模型计算机内存模型缓存一致性MESI协议可见性原子性顺序性happens-before内存屏障synchron...

qq_26761587的博客 7660

为什么要去学习底层的知识

为什么要去学习底层的知识 我从前有一个大的误区,认为学习一些框架知识,能够实现业务功能就可以了. 完全没有必要去深挖底层.但是这两天,慢慢发现去了解底层知识的重要性. 有些东西,只有在遇到了,被困住很久,最后解决了.印象才会深. 下面,我会记录一些我在平常学习和工作中遇到的有关底层(对于我来讲…) js中执行顺序 有个同学,在编写js时遇到了,一个问题.他的js代码在执行一个函数时,出现unde...

学习真香 2430

学java要不要看底层代码_程序员是否需要学习底层知识

程序员是否需要学习底层知识?这两天每天中午午休前都会看一些《编程人生》。现在已经看了七八个人,这些人开始编程的时候,正好是计算机的起步期,所以对整个计算机的底层都比较了解。不知道这是不是这些人能够成为大师的一个必要条件,自己有没有必要在这方面努力一下。我想这也是很多程序员思考过的问题。现在分享一下我的思考,和大家一起探讨。——ps:这些人大部分在中学的时候就开始编程,而那个时候我们一般都正在为考试...

weixin_39862697的博客 783

开始底层知识学习

开始底层学习!!前言 前言 大学毕业已经快五年了。 五年的工作里,一直就是业务代码业务代码。总结太少。和大学时的自己对比一下,十分惭愧。 于是静下心来,好好整理一下自己缺少的知识。对应的看看书,理解并简要总结总结。 ...

wzy1222 277

程序员是否需要学习底层知识?

  ​     这两天每天中午午休前都会看一些《编程人生》。现在已经看了七八个人,这些人开始编程的时候,正好是计算机的起步期,所以对整个计算机的底层都比较了解。不知道这是不是这些人能够成为大师的一个必要条件,自己有没有必要在这方面努力一下。我想这也是很多程序员思考过的问题。现在分享一下我的思考,和大家一起探讨。——ps:郑州白癜风医院 这些人大部分在中学的时候就开始编程,而那个时候我们...

weixin_30265171的博客 164
上一篇: 要读的书籍--暂时记下来
下一篇: java设计模式---适配器模式
给自己加加油
博客等级 码龄8年 302粉丝 110原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值