gprof 使用和介绍

一、gprof介绍

       gprofGNU profiler工具。可以显示程序运行的“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间。也可以显示调用图,包括函数的调用关系,每个函数调用花费了多少时间。还可以显示注释的源代码,是程序源代码的一个复本,标记有程序中每行代码的执行次数

二、Gprof功能:

    打印出程序运行中各个函数消耗的时间,可以帮助程序员找出众多函数中耗时最多的函数。

    产生程序运行时候的函数调用关系,包括调用次数,可以帮助程序员分析程序的运行流程。有了函数的调用关系,这会让开发人员大大提高工作效率,不用费心地去一点点找出程序的运行流程,这对小程序来说可能效果不是很明显,但对于有几万,几十万代码量的工程来说,效率是毋庸置疑的!而且这个功能对于维护旧代码或者是分析Open Source来说那是相当诱人的,有了调用图,对程序的运行框架也就有了一个大体了解,知道了程序的“骨架“,分析它也就不会再那么茫然,尤其是对自己不熟悉的代码和Open Source。费话不多说了,让我们开始我们的分析之旅吧!

三、Gprof 实现原理:

   通过在编译和链接你的程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or “_mcount”  , or  “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。

       程序运行结束后,会在程序退出的路径下生成一个 gmon.out文件。这个文件就是记录并保存下来的监控数据。可以通过命令行方式的gprof或图形化的Kprof来解读这些数据并对程序的性能进行分析。

       另外,如果想查看库函数的profiling,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a 库,才可以产生库函数的profiling信息。如果想执行一行一行的profiling,还需要加入“-g”编译参数。

四、gprof 的适用范围

       gprof可以用来分析系统在运行时各函数调用的次数,耗时等情况,可以方便地帮助我们定位系统的瓶颈,同时也能让我们知道对程序的那个位置就行优化能够带来尽可能大的性能提升。gprof 优化尤其适用于CPU、内存密集性的应用模块

五、gprof的安装使用

       目前我们的linux主机上大多都安装了gprof,详细的参数等可以通过man gprof查看。需要重点指出的是,目前我们线上的gprof对多线程的支持不好,直接调用只能得到主线程的相关调用情况。根据相关资料,原因为gprof采用ITIMER_PROF 信号,在多线程内,只有主线程才能响应该信号。为此,需要做一些额外的工作。使用提供的gprof-helper.c,将其编译为so命令为:gcc -shared -fPICgprof-helper.c -o gprof-helper.so -lpthread -ldl

       这个库的作用实际上实现一个pthread_create 的钩子程序,这样我们在调用pthread_create函数的时候就会调用到这个库中提供的pthread_create的函数,从而实现在多线程情况下统计运行时的相关信息。

       在实际使用中,方法比较简单,在我们自己的程序的makefile文件中,加上编译的选项 -pg,并加上那个动态链接库。如gcc -pg imbs_main.cpp ../gprof-helper.so$(INCLUDE) $(LDFLAGS) $(LDLIBS)

       这样在编译后会生成一个a.out 文件。这个文件就是包含了相关统计功能的可执行文件,和我们正常编译的程序在对外行为上是完全一致的。

参考资料: http://blog.csdn.net/baqiao10/articles/443495.aspx

      程序运行并“正常”退出后,会生成一个gmon.out文件,这个就是运行时的统计文件。使用命令gprof -ba.out gmon.out 就可以将最终我们readable的信息输出来。这些信息可以作为我们优化的依据。

      注意:上面提到的“正常”退出是指程序是按照自身的运行逻辑正常退出的,如果直接killall -9 是不能得到统计结果的。而我们通常的程序都是在循环中长时间运行,所以,实际中采用了相应 SIGTERM信号的方式,使用killall-s 15 ,发送SIGTERM信号给程序,程序中会有相应的函数捕捉该信号,捕捉到该信号后,置一个退出标记,这样我们就可以控制程序按照既定的逻辑在处理完一次完整的工作后正常的退出。

      这又引出了另一个问题,实际上我们现在上线程序,重启程序的时候,通常都是使用killall -9来停止原有程序的。这实际上是存在较大风险的,举例来说,如果程序在执行时存在一些持久化的操作,比如写磁盘,同时,写磁盘操作是多次完成,比如先写数据、再写索引等,这应该是一个在逻辑上的原子操作,那么killall -9的随机性可能破坏其原子性,从而造成潜在的数据不一致,如在我们常用的transfer中就存在这种数据不一致的隐患。虽然出现的概率不是很高,但是,长期的积累这种不一致性是会慢慢体现出来的。

 

六、Gprof基本用法:

1.使用-pg选项编译和链接你的应用程序。
2.
执行你的应用程序,使之运行完成后生成供gprof分析的数据文件(默认是gmon.out)。
3.
使用gprof程序分析你的应用程序生成的数据,例如:gprof a.out gmon.out

举例

gcc-Wall -pg -o testtest.c              //程序文件名称 test.编译时使用 –pg

       现在我们可以再次运行test,并使用我们前面使用的测试数据。这次我们运行的时候,test运行的分析数据会被搜集并保存在'gmon.out'文件中,我们可以通过运行 ' gprof test '来查看结果。

./test

gproftest

七、gprof产生的信息

                      the percentage of the total running time of the
time                    program used by this function.
                          
函数使用时间占所有时间的百分比。
cumulative          a running sumof the number of seconds accounted
seconds            for by this function and those listed above it.
                          
函数和上列函数累计执行的时间。
self                   the number of seconds accounted for by this
seconds            function alone. This is the major sort for this
                         listing.
                         
函数本身所执行的时间。
calls                  the number of times this function was invoked, if
                         this function is profiled, else blank.
                         
函数被调用的次数
self                  the average number of milliseconds spent in this
ms/call              function per call, if this function is profiled,
                        else blank.
                         
每一次调用花费在函数的时间microseconds
total                 the average number of milliseconds spent in this
ms/call              function and its descendents per call, if this
                         function is profiled, else blank.
                         
每一次调用,花费在函数及其衍生函数的平均时间microseconds
name                the name of the function. This is the minor sort
                         for this listing. The index shows the location of
                         the function in the gprof listing. If the index is
                         in parenthesis it shows where it would appear in
                         the gprof listing if it were to be printed.
                         
函数名

 

九、Gprof 简单使用:

让我们简单的举个例子来看看Gprof是如何使用的。

9.1.打开linux终端。

新建一个test.c文件,并生用-pg 编译和链接该文件。 test.c 文件内容如下:

 

        

#include

#include

void a()

{

      printf("\t\t+---call a() function\n");

 

}

 

void c()

{

      printf("\t\t+---call c() function\n");

}

int b()

{

printf("\t+--- call b()     function\n");

a();

c();

return 0;

}

int main()

{

      printf(" main() function()\n");

      b();

      return 0;

}

 

 

 

命令行里面输入下面命令,没加-c选项,gcc 会默认进行编译并链接生成a.out:

 

 

 

        

[linux /home/test]$gcc -pg test.c

 

 

 

    入果没有编译错误,gcc会在当前目录下生成一个a.out文件,当然你也可以使用 –o 选项给生成的文件起一个别的名字,像 gcc –pg test.c –o test, 则gcc会生成一个名为test的可执行文件,在命令行下输入[linux/home/test]$./test , 就可以执行该程序了,记住一定要加上 ./ 否则程序看上去可能是执行,可是什么输出都没有。

9.2.执行你的应用程序使之生成供gprof分析的数据。 命令行里面输入:

 

        

[linux /home/test]$a.out

main() function()

+--- call b() function

+---call a() function

+---call c() function

[linux /home/test]$

 

 

 

 

 

 

 


    你会在当前目录下看到一个gmon.out 文件, 这个文件就是供gprof 分析使用的。

9.3.使用gprof程序分析你的应用程序生成的数据。

令行里面输入:

 

        

[linux /home/test]$ gprof -b a.out gmon.out |     less

 

 

 

 


 

        

        cumulative        self                  self     total
     time   seconds     seconds        calls  Ts/call  Ts/call  name
     0.00      0.00         0.00        1         0.00     0.00  a
     0.00      0.00         0.00        1         0.00     0.00  b
     0.00      0.00         0.00        1         0.00     0.00  c

Call graph

granularity: each sample hit covers 4     byte(s) no time propagated

index %     time    self  children        called     name
     0.00    0.00           1/1           b [2]
     [1]      0.0        0.00    0.00           1         a [1]
     -----------------------------------------------
     0.00    0.00           1/1           main [10]
     [2]      0.0        0.00    0.00           1         b [2]
     0.00    0.00           1/1           a [1]
     0.00    0.00           1/1           c [3]
     -----------------------------------------------
     0.00    0.00           1/1           b [2]
     [3]      0.0        0.00    0.00           1         c [3]

     由于gprof 输出的信息比较多, 这里使用了 less  命令,该命令可以让我们通过上下方向建查看gprof产生的输出,| 表示gprof -b a.out gmon.out 的输出作为 less的输入。下面是我从gprof输出中摘抄出的与我们有关的一些详细信息。

 

 

 

 

 

 

 

 

 

 

 

 

gprof产生的信息解释如下:

    从上面的输出我们能明显的看出来,main 调用了 b 函数, 而b 函数分别调用了a 和 c 函数。由于我们的函数只是简单的输出了一个字串,故每个函数的消耗时间都是0 秒。

    到这为止你可能对gprof 有了一个比较感性的认识了,你可能会问如何用它去分析一个真正的Open Source 呢!下面就让我们去用gprof去分析一个Open Source,看看如何去在真实的环境中使用它。

 

十、使用Gprof 分析 Cflow开源项目

         CFlow是程序流程分析工具,该工具可以通过分析C源代码,产生程序调用图!有点跟Gprof差不多,不过CFlow是通过源代码进行的静态分析并且 不能分析C++ 程序,你可以到http://www.gnu.org/software/cflow/去下载源代码。

    假设你已经下载了该源代码(cflow-1.1.tar.gz),并把它放置在/home目录下,让我们看看如何在这个应用上使用gprof。

[linux /home/]tar zxvf     cflow-1.1.tar.gz

[linux     /home/cflow-1.1/src]$./configure

[linux /home]$make CFLAGS=-pg LDFLAGS=-pg

10.1.   使用  -pg  编译和链接该应用程序 , 请输入下列命令。

 

 

 

    如果没有出错你会在/home/cflow-1.1/src 目录下发行一个名为cflow的可执行文件,这就是我们加入-pg编译选项后编译出来的可以产生供gprof提取信息的可执行文件。记住一定要在编译和链接的时候都使用-pg选项否则可能不会产生gmon.out文件。对于cflow项目,CFLAGS=-pg 是设置它的编译选项,LDFLAGS=-pg是设置它的链接选项。当然你也可以直接修改它的Makefile来达到上述相同的目的,不过一定要记住编译和链接都要使用-pg选项。

[linux     /home/cflow-1.1/src]$cflow parser.c

10.2.   运行 cflow  程序使之生成 gmon.out  文件供 gprof 使用。

 

    查看/home/cflow-1.1/src目录下有没有产生gmon.out文件,如果没有请重复第一步,并确认你已经在编译和链接程序的时候使用了-pg选项。Cflow的使用请参考http://www.gnu.org/software/cflow/manual/cflow.html

10.3 使用gprof分析程序

 

        

[linux     /home/cflow-1.1/src]$gprof     -b cflow gmon.out | less

 

 

 

 

        

  cumulative       self                  self     total
     time   seconds   seconds    calls      Ts/call  Ts/call  name
     0.00      0.00         0.00   118262         0.00     0.00  include_symbol
     0.00      0.00         0.00    92896         0.00     0.00  is_printable
     0.00      0.00         0.00    28704     0.00         0.00  set_level_mark
     0.00      0.00         0.00    28703         0.00     0.00  is_last
     0.00      0.00         0.00    19615         0.00     0.00  auto_processor
     0.00      0.00         0.00    15494         0.00     0.00  gnu_output_handler
     0.00      0.00         0.00    12286         0.00     0.00  delete_parm_processor
     0.00      0.00         0.00     7728         0.00     0.00  newline
     0.00      0.00         0.00     7728         0.00     0.00  print_function_name
     0.00      0.00         0.00     7728         0.00     0.00  print_level

。。。。。。
     
。。。。。。

Call graph

granularity: each sample hit covers 4     byte(s) no time propagated

index % time        self  children   called     name
     [1]      0.0        0.00    0.00     79+855   
[1]
     0.00    0.00    166         dcl  [52]
     0.00    0.00    163         parse_dcl  [53]
     0.00    0.00    150         dirdcl  [56]
     0.00    0.00    129         parse_declaration  [63]
     0.00    0.00         98     parse_variable_declaration  [66]
     0.00    0.00         63     maybe_parm_list      [69]
     0.00    0.00     63        parse_function_declaration  [70]
     0.00    0.00     39        func_body  [74]

。。。。。。
     
。。。。。。


    
恭喜你,不出意外你会在屏幕上看到gprof 的输出,函数消耗时间和函数调用图,下面是我从我的输出中摘抄出来的一小段。

 

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    通过分析%time你就知道了那个函数消耗的时间最多,你可以根据这个输出信息做有目的的优化,不过cflow执行的速度是在是太快了,以至%time都是0 (消耗时间是以秒为单位进行统计的)。

10.4 生成图形化的函数调用图

 

        

1 digraph G {

2   node1;

3   node2;

4   node3;

6   node1 -> node2     [label="edge_1_2"];

7   node1 -> node3     [label="edge_1_3"];

8   node2 -> node3     [label="edge_2_3"];

9 }

1 .Graphviz  工具

    看到这里你也可能觉得上面的函数调用图实在是不方便察看,也看不出来一个程序调用的整体框架。没有关系,我再介绍一个有用的工具给你,使用 Graphviz ,Graphviz or Graph Visualization  是由 AT&T  开发的一个开源的图形可视化工具。它提供了多种画图能力,但是我们重点关注的是它使用 Dot 语言直连图的能力。在这里,将简单介绍如何使用 Dot 来创建一个图形,并展示如何将分析数据转换成 Graphviz 可以使用的规范, Dot 使用的图形规范。

    使用 Dot 语言,你可以指定三种对象:图、节点和边。为了让你理解这些对象的含义,我将构建一个例子来展示这些元素的用法。

 

 

    下图给出了一个简单的定向图(directed graph),其中包含 3 个节点。第一行声明这个图为 G,并且声明了该图的类型(digraph)。接下来的三行代码用于创建该图的节点,这些节点分别名为 node1、node2 和 node3。节点是在它们的名字出现在图规范中时创建的。边是在在两个节点使用边操作(->)连接在一起时创建的,如第 6 行到第 8 行所示。我还对边使用了一个可选的属性 label,用它来表示边在图中的名称。最后,在第 9 行完成对该图规范的定义。
使用 Dot 符号表示的示例图(test.dot)

 

 

 

 

 

    将这个 .dot 文件转换成一个图形映像,则需要使用 Dot 工具,这个工具是在 Graphviz 包中提供的。清单 6 介绍了这种转换。
清单 6. 使用 Dot 来创建 JPG 映像

 

        

[linux     /home]$ dot -T jpg test.dot -o test.jpg

 

 

 

    在这段代码中,我告诉 Dot 使用 test.dot 图形规范,并生成一个 JPG 图像,将其保存在文件 test.jpg 中。所生成的图像如图1所示。在此处,我使用了 JPG 格式,但是 Dot 工具也可以支持其他格式,其中包括 GIF、PNG 和 postscript等等。

 

        
 

 

 

    Dot 语言还可以支持其他一些选项,包括外形、颜色和很多属性。有兴趣可以查看graphviz相关文档。

    2从gprof的输出中提取调用图信息,产生可供Graphviz使用的dot文件。
这样的脚本有人已经实现了,我们只要下载一个现成的就可以了,首先从http://www.ioplex.com/~miallen/ 网站下载一个mkgraph脚本。解压该脚本到包含gmon.out文件的目录下。使用mkgraph0.sh产生调用的jpg图像文件。例如:使用上面的例子,生成cflow的调用图。
[linux /home/cflow-1.1/src]$ mkgraph0.sh cflow gmon.out
    部分调用图如下,有了这个图是不是对程序整体框架有了个清晰地了解,如果你对生成的调用图效果不满意,你还可以通过修改mkgraph0脚本使之产生合适的dot文件即可:

 

部分调用图如下

总结:
使用gprof , Graphviz , mkgraph生成函数调用图
1. 使用 -pg 编译和链接你的应用程序。
2. 执行你的应用程序使之生成供gprof 分析的数据。
3. 使用mkgraph脚本生成图形化的函数调用图。

相关资料:
文档:用 Graphviz 可视化函数调用
文档:Speed your code with the GNU profiler
文档:gropf 帮助文件
Mkgraph 脚本:http://www.ioplex.com/~miallen/
Graphviz 工具:http://www.graphviz.org
Cflow         :http://www.gnu.org/software/cflow/

 

十一、常见的优化手段

关于具体的优化,这个可能就不是一点篇幅就能写得下了,提一些常见的经验供参考。

      1. 循环次数很多的循环体内部,这种优化是收益最高的一种优化之一。循环体内部节约n条指令,最终收益将乘以循环次数。常见的如将循环体内不改变的变量移动到循环体外,多重循环嵌套时的顺序问题。

    2. 优化函数调用。这是优化提高非常明显的一种方式,常见的方式如函数inline这样可以节约大量的函数调用的开销,避免多次的压栈出栈。需要注意的是,如果makefile中不指定至少-O优化,即使声明为inline函数,编译器也不会进行函数内联。

      3. 优化内存寻址,避免重复的内存寻址。

4. 优化cpu流水线的分支预测的成功率。比如,如果当前存在有 ifelse ifelse语句,将满足概率最高的条件放到靠前的位置不仅能减少判断和跳转指令的执,还有利于cpu在流水作业时分支预测的成功率,提高指令的流水化。

    34单独优化的提升不会很大,但是如果是在大量的循环体内部,提升就会被放大很多倍。

 

十二、更多详细介绍

1.    在内存中分配一些内存,存储程序执行期间的统计数据

2.   GCC使用-pg选项编译后,gcc会在程序的入口处(main 函数之前)调用

voidmonstartup(lowpc, highpc)
在每个函数的入口处调用
    void _mcount()
在程序退出时(atexit () )调用
    void _mcleanup()

    * monstartup
:负责初始化profile环境,分配内存空间
    * _mcount: 
记录每个函数代码的callercallee的位置
    * _mcleanup
:清除profile环境,保存结果数据为gmon.out,供gprof分析结果
3
.在_mcount函数中跟踪程序的执行状况,记录程序代码的执行次数,时间等数据。

常用的gprof命令选项:

-b                不再输出统计图表中每个字段的详细描述。
-p                
只输出函数的调用图(Call graph的那部分信息)。
-q                
只输出函数的时间消耗列表。
-e Name       
不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e标志。一个 -e 标志只能指定一个函数。
-E Name       
不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。
-f Name       
输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数
-F Name       
输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。
-z                 
显示使用次数为零的例程(按照调用计数和累积时间计算)。

使用注意:
1
一般gprof只能查看用户函数信息。如果想查看库函数的信息,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a库,才可以产生库函数的profiling信息
2
 gprof只能在程序正常结束退出之后才能生成程序测评报告,原因是gprof通过在atexit()里注册了一个函数来产生结果信息,任何非正常退出都不会执行atexit()的动作,所以不会产生gmon.out文件。如果你的程序是一个不会退出的服务程序,那就只有修改代码来达到目的。如果不想改变程序的运行方式,可以添加一个信号处理函数解决问题(这样对代码修改最少),例如:

    static void sighandler( int sig_no )
    {
    exit(0);
    }
    signal( SIGUSR1, sighandler )

当使用kill -USR1 pid 后,程序退出,生成gmon.out文件。
编辑其他 C/C++ 程序分析器
还有其他很多分析器可以使用gprof 的数据例如                                      KProf (截屏cgprof虽然图形界面的看起来更舒服,但我个人认为命令行的gprof 使用更方便。

为了您的安全,请只打开来源可靠的网址

打开网站    取消

来自http://hi.baidu.com/zojoyo/blog/item/0ad631ee9aee302e2cf5341f.html

 

 

 

同时,-pg参数只能记录源代码中各个函数的调用关系,而不能记录库函数的调用情况。要想记录每个库函数的调用情况,链接的时候必须指定库函数的动态(或者静态)链接库libc_p.a,即加上-lc_p,而不是-lc

 

  还要说明的是,如果有一部分代码在编译时指定了-pg参数,而另一部分代码没有指定,则生成的gmon.out文件中将缺少一部分函数,也没有那些函数的调用关系。但是并不影响gprof对其它函数进行记录。

 

  运行

 

  编译好的程序运行时和运行一般的程序没有什么不同,只是比正常的程序多生成了一个文件gmon.out注意,这个文件名是固定的,没法通过参数的设置进行改变。如果程序目录中已经有一个gmon.out,则它会被新的gmon.out覆盖掉。

 

  关于生成的gmon.out文件所在的目录,也有以下约定:程序退出时所运行的文件所在目录就是生成的gmon.out文件所在的目录。如果一个程序执行过程中调用了另一个程序,并在另一个程序的运行中终止,则gmon.out会在另一个程序所在的目录中生成。

 

  还有一点要注意的就是当程序非正常终止时不会生成gmon.out文件,也因此就没法查看程序运行时的信息。只有当程序从main函数中正常退出,或者通过系统调用exit()函数而退出时,才会生成gmon.out文件。而通过底层调用如_exit()等退出时不会生成gmon.out

 

  查看

 

  查看程序运行信息的命令是gprof,它以gmon.out文件作为输入,也就是将gmon.out文件翻译成可读的形式展现给用户。其命令格式如下:

 

gprof [可执行文件][gmon.out文件] [其它参数]

 

  方括号中的内容可以省略。如果省略了“可执行文件”,gprof会在当前目录下搜索a.out文件作为可执行文件,而如果省略了gmon.out文件,gprof也会在当前目录下寻找gmon.out。其它参数可以控制gprof输出内容的格式等信息。最常用的参数如下:

 

l -b 不再输出统计图表中每个字段的详细描述。

 

l -p 只输出函数的调用图(Call graph的那部分信息)。

 

l -q 只输出函数的时间消耗列表。

 

l -eName 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。

 

l -EName 不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。

 

l -f Name 输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。

 

l -FName 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。

 

l -z 显示使用次数为零的例程(按照调用计数和累积时间计算)。



转自:http://blog.sina.com.cn/s/blog_4a471ff601013vud.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值