<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>严振杰</title>
  
  <subtitle>个人技术栈</subtitle>
  <link href="https://www.yanzhenjie.com/atom.xml" rel="self"/>
  
  <link href="https://www.yanzhenjie.com/"/>
  <updated>2025-10-16T02:12:31.814Z</updated>
  <id>https://www.yanzhenjie.com/</id>
  
  <author>
    <name>严振杰</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>让子弹飞一会儿</title>
    <link href="https://www.yanzhenjie.com/post/20251015/e84871e2150b/"/>
    <id>https://www.yanzhenjie.com/post/20251015/e84871e2150b/</id>
    <published>2025-10-15T15:25:37.000Z</published>
    <updated>2025-10-16T02:12:31.814Z</updated>
    
    <content type="html"><![CDATA[<p>你有没有思考过你性格的弱点是什么？</p><p>我认为我缺的是<font color="red">“耐受力”</font>。</p><p>一定要培养对事情玄而未觉的耐受力，<br>避免因急于求成而造成焦虑和烦恼，<br>要允许事情处于灰色地带，<br>接受事情的不可控因素，<br>过度追求掌控反而容易陷入焦虑与自我消耗，<br>任何问题的到来，<br>你都不需要太着急去回应，<br>否则就是在和自己死磕。<br>学会让子弹飞一会儿是大智慧，<br>事实上不管多大的事情，<br>到最后都会被解决，<br>不要给自己太大的压力，<br>你的人生也绝对不会因为任何一件单一的事情就毁掉。</p><p>以上，与诸君共勉，祝好~</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;你有没有思考过你性格的弱点是什么？&lt;/p&gt;
&lt;p&gt;我认为我缺的是&lt;font color=&quot;red&quot;&gt;“耐受力”&lt;/font&gt;。&lt;/p&gt;
&lt;p&gt;一定要培养对事情玄而未觉的耐受力，&lt;br&gt;避免因急于求成而造成焦虑和烦恼，&lt;br&gt;要允许事情处于灰色地带，&lt;br&gt;接受事情的不可控因</summary>
      
    
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/categories/%E6%84%9F%E6%83%B3/"/>
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/tags/%E6%84%9F%E6%83%B3/"/>
    
  </entry>
  
  <entry>
    <title>本地部署IndexTTS-2-Demo</title>
    <link href="https://www.yanzhenjie.com/post/20251001/87550f62e1a8/"/>
    <id>https://www.yanzhenjie.com/post/20251001/87550f62e1a8/</id>
    <published>2025-10-01T15:19:03.000Z</published>
    <updated>2025-10-02T08:28:15.919Z</updated>
    
    <content type="html"><![CDATA[<p>IndexTTS2 是 B 站于 2025 年 9 月开源的，9 月份一直想玩一下，但是 9 月工作太忙了没有尝试，国庆期间趁热打铁来本地部署玩一下。</p><p>IndexTTS2 是首个自回归零样本 TTS 系统，支持精准时长控制、音色情感解耦、多模态输入等创新功能，可通过官网获取源码及技术文档。</p><p>特色：</p><ul><li>音色克隆：仅需 10 秒参考音频即可精准还原音色、口音及说话节奏。</li><li>音色与情感解耦：支持独立指定音色来源和情绪来源，实现音色与情感的分离控制。</li><li>情绪调节：在音色与情感解耦基础上，通过参考音频或文本描述调整愤怒、喜悦等情绪权重。</li><li>多模态输入：支持文本描述、音频提示或情感向量控制语调，覆盖音色还原与情绪调节的输入方式。</li><li>影视级应用：音画同步误差&lt;0.02%，适配视频配音场景。</li><li>实践案例：已应用于《让子弹飞》《甄嬛传》等经典影视作品 AI 配音。</li></ul><h2 id="1、项目获取"><a href="#1、项目获取" class="headerlink" title="1、项目获取"></a>1、项目获取</h2><p>有 2 种获取方式。</p><ol><li><p>如果你可以访问 huggingface，那么：</p><ul><li>IndexTTS-2：<a class="link"   href="https://huggingface.co/IndexTeam/IndexTTS-2" >https://huggingface.co/IndexTeam/IndexTTS-2<i class="fas fa-external-link-alt"></i></a></li><li>IndexTTS-2-Demo：<a class="link"   href="https://huggingface.co/spaces/IndexTeam/IndexTTS-2-Demo" >https://huggingface.co/spaces/IndexTeam/IndexTTS-2-Demo<i class="fas fa-external-link-alt"></i></a></li></ul></li><li><p>如果不能访问 huggingface，那么访问国内的 modelscope：</p><ul><li>IndexTTS-2：<a class="link"   href="https://www.modelscope.cn/models/IndexTeam/IndexTTS-2" >https://www.modelscope.cn/models/IndexTeam/IndexTTS-2<i class="fas fa-external-link-alt"></i></a></li><li>IndexTTS-2-Demo：<a class="link"   href="https://www.modelscope.cn/studios/IndexTeam/IndexTTS-2-Demo" >https://www.modelscope.cn/studios/IndexTeam/IndexTTS-2-Demo<i class="fas fa-external-link-alt"></i></a></li></ul></li></ol><p>以上 2 个 Demo 地址都是可以在线体验的。</p><h2 id="2、本地安装"><a href="#2、本地安装" class="headerlink" title="2、本地安装"></a>2、本地安装</h2><p>本地部署非常简单，但是中间会遇到 openfst 和 pynini 的一些编译问题。</p><p>我的环境：</p><ul><li>MacOS Sequia 15.7</li><li>Command_Line_Tools_for_Xcode_16.4_MacOS_15.3</li><li>Homebrew 4.6.15</li><li>pyenv 2.6.8</li><li>Python 3.10.12</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">brew list</span></span><br><span class="line">==&gt; Formulae</span><br><span class="line">autoconf        gettext         libunistring    m4              openfst         pkgconf         readline        wget            zlib</span><br><span class="line">bzip2           libidn2         llvm            mpdecimal       openssl@1.1     pyenv           sqlite          xz              zstd</span><br><span class="line">ca-certificates libtommath      lz4             ncurses         openssl@3       python@3.13     tcl-tk          z3</span><br><span class="line"></span><br><span class="line">==&gt; Casks</span><br></pre></td></tr></table></figure><p>把项目拉到本地后，激活 python 环境，安装依赖：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install -r requirements.txt</span><br></pre></td></tr></table></figure><p>当然，肯定是失败了，很顺利的话就不会有这篇文章了，让我们来看看发生了什么：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">copying pynini/export/py.typed -&gt; build/lib.macosx-15.7-arm64-cpython-39/pynini/export</span><br><span class="line">running build_ext</span><br><span class="line">building &#x27;_pywrapfst&#x27; extension</span><br><span class="line">creating build/temp.macosx-15.7-arm64-cpython-39/extensions</span><br><span class="line">clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -DOPENSSL_NO_SSL3 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/Users/Harry/Workspace/ModelScope/IndexTTS-2-Demo/.venv/include -I/Users/Harry/.pyenv/versions/3.9.23/include/python3.9 -c extensions/_pywrapfst.cpp -o build/temp.macosx-15.7-arm64-cpython-39/extensions/_pywrapfst.o -std=c++17 -Wno-register -Wno-deprecated-declarations -Wno-unused-function -Wno-unused-local-typedefs -funsigned-char -stdlib=libc++ -mmacosx-version-min=10.12</span><br><span class="line">extensions/_pywrapfst.cpp:1191:10: fatal error: &#x27;fst/util.h&#x27; file not found</span><br><span class="line">  1191 | #include &lt;fst/util.h&gt;</span><br><span class="line">      |          ^~~~~~~~~~~~</span><br><span class="line">1 error generated.</span><br><span class="line">error: command &#x27;/usr/bin/clang++&#x27; failed with exit code 1</span><br><span class="line">[end of output]</span><br><span class="line"></span><br><span class="line">  note: This error originates from a subprocess, and is likely not a problem with pip.</span><br><span class="line">  ERROR: Failed building wheel for pynini</span><br><span class="line">Failed to build pynini</span><br><span class="line">ERROR: Could not build wheels for pynini, which is required to install pyproject.toml-based projects</span><br></pre></td></tr></table></figure><p>核心是<code>&#39;fst/util.h&#39; file not found</code>，应该不存在 openfst，那么安装<code>brew install openfst</code>，然后再次<code>pip install -r requirements.txt</code>，依旧失败：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">building &#x27;_pywrapfst&#x27; extension</span><br><span class="line">creating build/temp.macosx-15.7-arm64-cpython-39/extensions</span><br><span class="line">clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -DOPENSSL_NO_SSL3 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/Users/Harry/Workspace/ModelScope/IndexTTS-2-Demo/.venv/include -I/Users/Harry/.pyenv/versions/3.9.23/include/python3.9 -c extensions/_pywrapfst.cpp -o build/temp.macosx-15.7-arm64-cpython-39/extensions/_pywrapfst.o -std=c++17 -Wno-register -Wno-deprecated-declarations -Wno-unused-function -Wno-unused-local-typedefs -funsigned-char -stdlib=libc++ -mmacosx-version-min=10.12</span><br><span class="line">clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -DOPENSSL_NO_SSL3 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -bundle -undefined dynamic_lookup build/temp.macosx-15.7-arm64-cpython-39/extensions/_pywrapfst.o -L/Users/Harry/.pyenv/versions/3.9.23/lib -lfstfarscript -lfstfar -lfstscript -lfst -lm -ldl -o build/lib.macosx-15.7-arm64-cpython-39/_pywrapfst.cpython-39-darwin.so</span><br><span class="line">ld: library &#x27;fstfarscript&#x27; not found</span><br><span class="line">clang++: error: linker command failed with exit code 1 (use -v to see invocation)</span><br><span class="line">error: command &#x27;/usr/bin/clang++&#x27; failed with exit code 1</span><br><span class="line">[end of output]</span><br><span class="line"></span><br><span class="line">note: This error originates from a subprocess, and is likely not a problem with pip.</span><br><span class="line">ERROR: Failed building wheel for pynini</span><br><span class="line">Failed to build pynini</span><br><span class="line">error: failed-wheel-build-for-install</span><br><span class="line"></span><br><span class="line">× Failed to build installable wheels for some pyproject.toml based projects</span><br><span class="line">╰─&gt; pynini</span><br></pre></td></tr></table></figure><p>看着是 pynini 编译安装失败了，然后还注意到<code>library &#39;fstfarscript&#39; not found</code>，网上查资料看是 openfst 和 pynini 有不匹配，其实从日志可以看出来不是缺少库，而是库里面缺少<code>fstfarscript</code>，有答案推荐安装 openfst 的开发版：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装openfst开发版</span></span><br><span class="line">brew reinstall openfst --build-from-source</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置连接器路径</span></span><br><span class="line">export LDFLAGS=&quot;-L$(brew --prefix openfst)/lib $LDFLAGS&quot;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">手动尝试安装pynini</span></span><br><span class="line">pip install pynini --no-cache-dir --global-option=build_ext --global-option=&quot;-L$(brew --prefix openfst)/lib&quot;</span><br></pre></td></tr></table></figure><p>OK，这里 pynini 安装成功了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Looking in indexes: https://mirrors.aliyun.com/pypi/simple/, https://pypi.tuna.tsinghua.edu.cn/simple/, https://mirrors.ustc.edu.cn/pypi/simple/, https://pypi.org/simple/</span><br><span class="line">Processing /Users/Harry/Workspace/ModelScope/IndexTTS-2-Demo/pynini-2.1.7</span><br><span class="line">  Installing build dependencies ... done</span><br><span class="line">  Getting requirements to build wheel ... done</span><br><span class="line">  Preparing metadata (pyproject.toml) ... done</span><br><span class="line">Building wheels for collected packages: pynini</span><br><span class="line">  Building wheel for pynini (pyproject.toml) ... done</span><br><span class="line">  Created wheel for pynini: filename=pynini-2.1.7-cp310-cp310-macosx_15_0_arm64.whl size=2023561 sha256=e2a86e63d5dfe7de6ef277e56d6d1f08016003410948b21259dc05b2c72e8b69</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/81/07/cb/9d3f76d8da6b8b6791674ef8dc617e86bba48058c1165d11bb</span><br><span class="line">Successfully built pynini</span><br><span class="line">Installing collected packages: pynini</span><br><span class="line">Successfully installed pynini-2.1.7</span><br></pre></td></tr></table></figure><p>我很高兴的继续安装依赖<code>pip install -r requirements.txt</code>，实时证明我高兴的太早了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">      building &#x27;_pynini&#x27; extension</span><br><span class="line">      clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -DOPENSSL_NO_SSL3 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -Iextensions -I/Users/Harry/Workspace/ModelScope/IndexTTS-2-Demo/.venv/include -I/Users/Harry/.pyenv/versions/3.9.23/include/python3.9 -c extensions/_pynini.cpp -o build/temp.macosx-15.7-arm64-cpython-39/extensions/_pynini.o -std=c++17 -Wno-register -Wno-deprecated-declarations -Wno-unused-function -Wno-unused-local-typedefs -funsigned-char -stdlib=libc++ -mmacosx-version-min=10.12</span><br><span class="line">      In file included from extensions/_pynini.cpp:1252:</span><br><span class="line">      In file included from extensions/stringmapscript.h:31:</span><br><span class="line">      extensions/stringmap.h:185:26: error: no member named &#x27;StringJoin&#x27; in namespace &#x27;fst&#x27;; did you mean &#x27;StrJoin&#x27;?</span><br><span class="line">        185 |                  &lt;&lt; fst::StringJoin(line, &quot;\t&quot;) &lt;&lt; &quot;`&quot;;</span><br><span class="line">            |                     ~~~~~^~~~~~~~~~</span><br><span class="line">            |                          StrJoin</span><br><span class="line">      /opt/homebrew/opt/openfst/include/fst/compat.h:197:13: note: &#x27;StrJoin&#x27; declared here</span><br><span class="line">        197 | std::string StrJoin(const std::vector&lt;S&gt; &amp;elements, std::string_view delim) &#123;</span><br><span class="line">            |             ^</span><br><span class="line">      In file included from extensions/_pynini.cpp:1252:</span><br><span class="line">      In file included from extensions/stringmapscript.h:31:</span><br><span class="line">      extensions/stringmap.h:230:26: error: no member named &#x27;StringJoin&#x27; in namespace &#x27;fst&#x27;; did you mean &#x27;StrJoin&#x27;?</span><br><span class="line">        230 |                  &lt;&lt; fst::StringJoin(line, &quot;\t&quot;) &lt;&lt; &quot;`&quot;;</span><br><span class="line">            |                     ~~~~~^~~~~~~~~~</span><br><span class="line">            |                          StrJoin</span><br><span class="line">      /opt/homebrew/opt/openfst/include/fst/compat.h:197:13: note: &#x27;StrJoin&#x27; declared here</span><br><span class="line">        197 | std::string StrJoin(const std::vector&lt;S&gt; &amp;elements, std::string_view delim) &#123;</span><br><span class="line">            |             ^</span><br><span class="line">      2 errors generated.</span><br><span class="line">      error: command &#x27;/usr/bin/clang++&#x27; failed with exit code 1</span><br><span class="line">      [end of output]</span><br><span class="line"></span><br><span class="line">  note: This error originates from a subprocess, and is likely not a problem with pip.</span><br><span class="line">  ERROR: Failed building wheel for pynini</span><br><span class="line">  Building wheel for distance (pyproject.toml) ... done</span><br><span class="line">  Created wheel for distance: filename=distance-0.1.3-py3-none-any.whl size=16321 sha256=5b3994bb5686de9bbc7f78ce4f2e702b482f572e8620f0e8216d827950dabdae</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/fb/b3/aa/04241cced6d1722b132273b1d6aafba317887ec004f48b853a</span><br><span class="line">  Building wheel for argbind (pyproject.toml) ... done</span><br><span class="line">  Created wheel for argbind: filename=argbind-0.3.9-py2.py3-none-any.whl size=11816 sha256=d19bcc9a580acc68e2b638466adefc2ef26058064a3e8cab62acda610b7a539d</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/3e/9e/9c/0ab23ae19848099a030aaa8f68a3875e629671d859a40a7fc9</span><br><span class="line">  Building wheel for julius (pyproject.toml) ... done</span><br><span class="line">  Created wheel for julius: filename=julius-0.2.7-py3-none-any.whl size=21966 sha256=fb8693a91aed2dd06389c064ff742fdb7f6d3d722ef79f5ad2f4707e10d178ab</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/53/0a/a7/fc08f97438f4969d86afa7904336c2eb7eb422101359f3ad11</span><br><span class="line">  Building wheel for randomname (pyproject.toml) ... done</span><br><span class="line">  Created wheel for randomname: filename=randomname-0.2.1-py3-none-any.whl size=89288 sha256=472a9c1becbf4931603c4affad5c5748b46dca930643b795721cc0ecf8265bae</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/32/f7/3b/a46d1bb5fd36d11db2d6c267513fb405f2457cbcd6b50c760f</span><br><span class="line">Successfully built jieba deepspeed antlr4-python3-runtime distance argbind julius randomname</span><br><span class="line">Failed to build pynini</span><br><span class="line">error: failed-wheel-build-for-install</span><br><span class="line"></span><br><span class="line">× Failed to build installable wheels for some pyproject.toml based projects</span><br><span class="line">╰─&gt; pynini</span><br></pre></td></tr></table></figure><p>openfst 和 pynini 都安装成功了，安装<code>requirements.txt</code>报错关键信息如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">In file included from extensions/stringmapscript.h:31:</span><br><span class="line">extensions/stringmap.h:185:26: error: no member named &#x27;StringJoin&#x27; in namespace &#x27;fst&#x27;; did you mean &#x27;StrJoin&#x27;?</span><br><span class="line">  185 |                  &lt;&lt; fst::StringJoin(line, &quot;\t&quot;) &lt;&lt; &quot;`&quot;;</span><br><span class="line">      |                     ~~~~~^~~~~~~~~~</span><br><span class="line">      |                          StrJoin</span><br></pre></td></tr></table></figure><p>看信息是安装 pynini extion 时失败了，从<code>error: no member named &#39;StringJoin&#39; in namespace &#39;fst&#39;; did you mean &#39;StrJoin&#39;?</code>看还是 pynini 和 openfst 不兼容的问题。在 OpenFST 1.8.0 中，<code>StringJoin</code>函数被重命名为<code>StrJoin</code>了，所以我们得修改 pynini 源码来安装了：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">卸载当前 pynini</span></span><br><span class="line">pip uninstall pynini -y</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载 pynini 源码</span></span><br><span class="line">wget https://github.com/yanzhenjie/pynini/archive/refs/tags/v2.1.7.tar.gz</span><br><span class="line">tar -xzf v2.1.7.tar.gz</span><br><span class="line">cd pynini-2.1.7</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">应用修复补丁 (将 StringJoin 替换为 StrJoin)</span></span><br><span class="line">sed -i &#x27;&#x27; &#x27;s/fst::StringJoin/fst::StrJoin/g&#x27; extensions/stringmap.h</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装pynini的依赖</span></span><br><span class="line">pip install -r requirements.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">重新安装修复后的 pynini</span></span><br><span class="line">pip install .</span><br></pre></td></tr></table></figure><blockquote><p><font color=red><b>特别注意：这里需要手动把本地 pynini 加入到 requirements.txt 中，否则还是会从远端安装。</b></font></p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./pynini-2.1.7</span><br><span class="line">accelerate==1.8.1</span><br></pre></td></tr></table></figure></blockquote><p>从源码安装 pynini 又成功了，紧接着我又开始继续安装项目依赖<code>pip install -r requirements.txt</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">Using cached wcwidth-0.2.14-py2.py3-none-any.whl (37 kB)</span><br><span class="line">Building wheels for collected packages: pynini</span><br><span class="line">  Building wheel for pynini (pyproject.toml) ... error</span><br><span class="line">  error: subprocess-exited-with-error</span><br><span class="line"></span><br><span class="line">  × Building wheel for pynini (pyproject.toml) did not run successfully.</span><br><span class="line">  │ exit code: 1</span><br><span class="line">  ╰─&gt; [68 lines of output]</span><br><span class="line">      /private/var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/pip-build-env-o0w8wif1/overlay/lib/python3.10/site-packages/setuptools/config/_apply_pyprojecttoml.py:82: SetuptoolsDeprecationWarning: `project.license` as a TOML table is deprecated</span><br><span class="line">      !!</span><br><span class="line"></span><br><span class="line">              ********************************************************************************</span><br><span class="line">              Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools&gt;=77.0.0).</span><br><span class="line"></span><br><span class="line">              By 2026-Feb-18, you need to update your project and remove deprecated calls</span><br><span class="line">              or your builds will no longer be supported.</span><br><span class="line"></span><br><span class="line">              See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.</span><br><span class="line">              ********************************************************************************</span><br><span class="line"></span><br><span class="line">      !!</span><br><span class="line">        corresp(dist, value, root_dir)</span><br><span class="line">      /private/var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/pip-build-env-o0w8wif1/overlay/lib/python3.10/site-packages/setuptools/config/_apply_pyprojecttoml.py:61: SetuptoolsDeprecationWarning: License classifiers are deprecated.</span><br><span class="line">      !!</span><br><span class="line"></span><br><span class="line">              ********************************************************************************</span><br><span class="line">              Please consider removing the following classifiers in favor of a SPDX license expression:</span><br><span class="line"></span><br><span class="line">              License :: OSI Approved :: Apache Software License</span><br><span class="line"></span><br><span class="line">              See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.</span><br><span class="line">              ********************************************************************************</span><br><span class="line"></span><br><span class="line">      !!</span><br><span class="line">        dist._finalize_license_expression()</span><br><span class="line">      /private/var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/pip-build-env-o0w8wif1/overlay/lib/python3.10/site-packages/setuptools/dist.py:759: SetuptoolsDeprecationWarning: License classifiers are deprecated.</span><br><span class="line">      !!</span><br><span class="line"></span><br><span class="line">              ********************************************************************************</span><br><span class="line">              Please consider removing the following classifiers in favor of a SPDX license expression:</span><br><span class="line"></span><br><span class="line">              License :: OSI Approved :: Apache Software License</span><br><span class="line"></span><br><span class="line">              See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.</span><br><span class="line">              ********************************************************************************</span><br><span class="line"></span><br><span class="line">      !!</span><br><span class="line">        self._finalize_license_expression()</span><br><span class="line">      running bdist_wheel</span><br><span class="line">      running build</span><br><span class="line">      running build_py</span><br><span class="line">      running egg_info</span><br><span class="line">      writing pynini.egg-info/PKG-INFO</span><br><span class="line">      writing dependency_links to pynini.egg-info/dependency_links.txt</span><br><span class="line">      writing top-level names to pynini.egg-info/top_level.txt</span><br><span class="line">      reading manifest file &#x27;pynini.egg-info/SOURCES.txt&#x27;</span><br><span class="line">      reading manifest template &#x27;MANIFEST.in&#x27;</span><br><span class="line">      adding license file &#x27;LICENSE&#x27;</span><br><span class="line">      adding license file &#x27;AUTHORS&#x27;</span><br><span class="line">      writing manifest file &#x27;pynini.egg-info/SOURCES.txt&#x27;</span><br><span class="line">      running build_ext</span><br><span class="line">      building &#x27;_pywrapfst&#x27; extension</span><br><span class="line">      clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/opt/homebrew/opt/openfst/include -I/Users/Harry/Workspace/ModelScope/IndexTTS-2-Demo/.venv/include -I/Users/Harry/.pyenv/versions/3.10.12/include/python3.10 -c extensions/_pywrapfst.cpp -o build/temp.macosx-15.7-arm64-cpython-310/extensions/_pywrapfst.o -std=c++17 -Wno-register -Wno-deprecated-declarations -Wno-unused-function -Wno-unused-local-typedefs -funsigned-char -stdlib=libc++ -mmacosx-version-min=10.12</span><br><span class="line">      In file included from extensions/_pywrapfst.cpp:1193:</span><br><span class="line">      In file included from /opt/homebrew/opt/openfst/include/fst/fstlib.h:57:</span><br><span class="line">      /opt/homebrew/opt/openfst/include/fst/const-fst.h:403:25: warning: comparison of integers of different signs: &#x27;int64_t&#x27; (aka &#x27;long long&#x27;) and &#x27;size_t&#x27; (aka &#x27;unsigned long&#x27;) [-Wsign-compare]</span><br><span class="line">        403 |     if (hdr.NumStates() != num_states) &#123;</span><br><span class="line">            |         ~~~~~~~~~~~~~~~ ^  ~~~~~~~~~~</span><br><span class="line">      /opt/homebrew/opt/openfst/include/fst/const-fst.h:407:23: warning: comparison of integers of different signs: &#x27;int64_t&#x27; (aka &#x27;long long&#x27;) and &#x27;size_t&#x27; (aka &#x27;unsigned long&#x27;) [-Wsign-compare]</span><br><span class="line">        407 |     if (hdr.NumArcs() != num_arcs) &#123;</span><br><span class="line">            |         ~~~~~~~~~~~~~ ^  ~~~~~~~~</span><br><span class="line">      2 warnings generated.</span><br><span class="line">      clang++ -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/opt/homebrew/opt/openfst/include -bundle -undefined dynamic_lookup -I/opt/homebrew/opt/openfst/include build/temp.macosx-15.7-arm64-cpython-310/extensions/_pywrapfst.o -L/Users/Harry/.pyenv/versions/3.10.12/lib -lfstfarscript -lfstfar -lfstscript -lfst -lm -ldl -o build/lib.macosx-15.7-arm64-cpython-310/_pywrapfst.cpython-310-darwin.so</span><br><span class="line">      ld: library &#x27;fstfarscript&#x27; not found</span><br><span class="line">      clang++: error: linker command failed with exit code 1 (use -v to see invocation)</span><br><span class="line">      error: command &#x27;/usr/bin/clang++&#x27; failed with exit code 1</span><br><span class="line">      [end of output]</span><br><span class="line"></span><br><span class="line">  note: This error originates from a subprocess, and is likely not a problem with pip.</span><br><span class="line">  ERROR: Failed building wheel for pynini</span><br><span class="line">Failed to build pynini</span><br><span class="line">error: failed-wheel-build-for-install</span><br><span class="line"></span><br><span class="line">× Failed to build installable wheels for some pyproject.toml based projects</span><br><span class="line">╰─&gt; pynini</span><br></pre></td></tr></table></figure><p>好家伙，还是链接错误<code>ld: library &#39;fstfarscript&#39; not found</code>，这是由于 OpenFST 的库命名在不同 Python 版本下行为不一致导致的，查了一下可能的原因：</p><blockquote><p>OpenFST 库命名变化：</p><ul><li>OpenFST 1.8.0+ 版本不再提供 libfstfarscript 库</li><li>它被拆分为 libfstfar 和 libfstscript 两个独立库</li><li>但在 pynini 的构建配置中，仍然尝试链接 fstfarscript</li></ul></blockquote><p>所以接下来我们让<code>-lfstfar -lfstscript</code>替代<code>-lfstfarscript</code>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">确保设置正确的 LDFLAGS</span></span><br><span class="line">export CFLAGS=&quot;-I$(brew --prefix openfst)/include $CFLAGS&quot;</span><br><span class="line">export CPPFLAGS=&quot;-I$(brew --prefix openfst)/include $CPPFLAGS&quot;</span><br><span class="line">export LDFLAGS=&quot;-L$(brew --prefix openfst)/lib -lfstfar -lfstscript $LDFLAGS&quot;</span><br></pre></td></tr></table></figure><p>重新安装 pynini：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cd pynini-2.1.7</span><br><span class="line">pip uninstall pynini -y</span><br><span class="line">pip install -r requirements.txt</span><br><span class="line">pip install .</span><br></pre></td></tr></table></figure><p>接下来到项目根目录安装依赖<code>pip install -r requirements.txt</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Looking in indexes: https://mirrors.aliyun.com/pypi/simple/, https://pypi.tuna.tsinghua.edu.cn/simple/, https://mirrors.ustc.edu.cn/pypi/simple/, https://pypi.org/simple/</span><br><span class="line">Processing ./pynini-2.1.7</span><br><span class="line">  Installing build dependencies ... done</span><br><span class="line">  ...</span><br><span class="line">Using cached wcwidth-0.2.14-py2.py3-none-any.whl (37 kB)</span><br><span class="line">Building wheels for collected packages: pynini</span><br><span class="line">  Building wheel for pynini (pyproject.toml) ... done</span><br><span class="line">  Created wheel for pynini: filename=pynini-2.1.7-cp310-cp310-macosx_15_0_arm64.whl size=2023566 sha256=97cba0df2440c1d0bf6c5ec910a40bc614d8e7ae41b815921dc3f0a086a68523</span><br><span class="line">  Stored in directory: /Users/Harry/Library/Caches/pip/wheels/81/07/cb/9d3f76d8da6b8b6791674ef8dc617e86bba48058c1165d11bb</span><br><span class="line">Successfully built pynini</span><br><span class="line">Installing ... spaces</span><br><span class="line">  Attempting uninstall: pynini</span><br><span class="line">    Found existing installation: pynini 2.1.7</span><br><span class="line">    Uninstalling pynini-2.1.7:</span><br><span class="line">      Successfully uninstalled pynini-2.1.7</span><br><span class="line">  Attempting uninstall: Cython</span><br><span class="line">    Found existing installation: Cython 3.1.4</span><br><span class="line">    Uninstalling Cython-3.1.4:</span><br><span class="line">      Successfully uninstalled Cython-3.1.4</span><br><span class="line">Successfully installed Cython-3.0.7 ... wetext-0.1.0</span><br></pre></td></tr></table></figure><p>成功了，✌🏻✌🏻✌🏻！！！</p><h3 id="2-1、问题修复汇总"><a href="#2-1、问题修复汇总" class="headerlink" title="2.1、问题修复汇总"></a>2.1、问题修复汇总</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">1. 安装 OpenFST</span></span><br><span class="line">brew install openfst</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">2. 获取 pynini 源码</span></span><br><span class="line">wget https://github.com/yanzhenjie/pynini/archive/refs/tags/v2.1.7.tar.gz</span><br><span class="line">tar -xzf v2.1.7.tar.gz</span><br><span class="line">cd pynini-2.1.7</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">3. 修复 stringmap.h</span></span><br><span class="line">sed -i &#x27;&#x27; &#x27;s/fst::StringJoin/fst::StrJoin/g&#x27; extensions/stringmap.h</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">4. 修复链接问题</span></span><br><span class="line">export CFLAGS=&quot;-I$(brew --prefix openfst)/include $CFLAGS&quot;</span><br><span class="line">export CPPFLAGS=&quot;-I$(brew --prefix openfst)/include $CPPFLAGS&quot;</span><br><span class="line">export LDFLAGS=&quot;-L$(brew --prefix openfst)/lib -lfstfar -lfstscript $LDFLAGS&quot;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">5. 安装 pynini</span></span><br><span class="line">pip uninstall pynini -y</span><br><span class="line">pip install -r requirements.txt .</span><br><span class="line">pip install .</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">6. 修改 requirements.txt，添加如下内容</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">./pynini-2.1.7</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">7. 在项目中安装其他依赖</span></span><br><span class="line">cd ..</span><br><span class="line">pip install -r requirements.txt</span><br></pre></td></tr></table></figure><h2 id="3、本地预览"><a href="#3、本地预览" class="headerlink" title="3、本地预览"></a>3、本地预览</h2><p>在项目根目录下运行<code>python app.py</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Looking in indexes: https://mirrors.aliyun.com/pypi/simple/, https://pypi.tuna.tsinghua.edu.cn/simple/, https://mirrors.ustc.edu.cn/pypi/simple/, https://pypi.org/simple/</span><br><span class="line">Collecting protobuf==3.20.3</span><br><span class="line">...</span><br><span class="line">modelscope - INFO - Got n files, start to download ...</span><br><span class="line">...</span><br><span class="line">modelscope - INFO - Download model &#x27;IndexTeam/IndexTTS-2&#x27; successfully.</span><br><span class="line">---model_id--- nvidia/bigvgan_v2_22khz_80band_256x</span><br><span class="line">Loading weights from nv-community/bigvgan_v2_22khz_80band_256x</span><br><span class="line">Removing weight norm...</span><br><span class="line">&gt;&gt; bigvgan weights restored from: nvidia/bigvgan_v2_22khz_80band_256x</span><br><span class="line">&gt;&gt; TextNormalizer loaded</span><br><span class="line">&gt;&gt; bpe model loaded from: ./checkpoints/bpe.model</span><br><span class="line">* Running on local URL:  http://192.168.1.3:7860</span><br><span class="line">* To create a public link, set `share=True` in `launch()`.</span><br></pre></td></tr></table></figure><p>这里根据网络情况的不同需要几十分钟，因为要下载很多模型文件，然后看到如上提示就可以本地访问了。</p><p><img   src="/post/20251001/87550f62e1a8/demo.png" ></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;IndexTTS2 是 B 站于 2025 年 9 月开源的，9 月份一直想玩一下，但是 9 月工作太忙了没有尝试，国庆期间趁热打铁来本地部署玩一下。&lt;/p&gt;
&lt;p&gt;IndexTTS2 是首个自回归零样本 TTS 系统，支持精准时长控制、音色情感解耦、多模态输入等创新功能，</summary>
      
    
    
    
    <category term="人工智能" scheme="https://www.yanzhenjie.com/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    
    
    <category term="IndexTTS-2" scheme="https://www.yanzhenjie.com/tags/IndexTTS-2/"/>
    
  </entry>
  
  <entry>
    <title>No rule to make target &#39;pyconfig.h&#39;</title>
    <link href="https://www.yanzhenjie.com/post/20251001/8c338a8e95f2/"/>
    <id>https://www.yanzhenjie.com/post/20251001/8c338a8e95f2/</id>
    <published>2025-10-01T12:42:26.000Z</published>
    <updated>2025-10-01T13:27:25.432Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>No rule to make target <code>pyconfig.h&#39;, needed by </code>Programs&#x2F;python.o’</p></blockquote><p>我日常使用 pyenv 管理不同的 Python 版本，最近在 MacOS 13.2 测试 IndexTTS 2 时发现编译环境需要升级（由于本文遇到的问题我怀疑不用升级） ，最近由 MacOS 13.2 升级到 MacOS 15.7 后，需要重装 pyenv 中的几个 python 版本。</p><p>我的环境：</p><ul><li>MacOS Sequia 15.7</li><li>Command_Line_Tools_for_Xcode_16.4_MacOS_15.3</li><li>Homebrew 4.6.15</li><li>pyenv 2.6.8</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">brew list</span></span><br><span class="line">==&gt; Formulae</span><br><span class="line">autoconf        ca-certificates llvm            m4              ncurses         openssl@1.1     pkgconf         python@3.13     sqlite          xz              zlib</span><br><span class="line">bzip2           libtommath      lz4             mpdecimal       openfst         openssl@3       pyenv           readline        tcl-tk          z3              zstd</span><br><span class="line"></span><br><span class="line">==&gt; Casks</span><br></pre></td></tr></table></figure><p>我的问题表现：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">pyenv install 3.9</span><br><span class="line">python-build: use openssl@3 from homebrew</span><br><span class="line">python-build: use readline from homebrew</span><br><span class="line">Downloading Python-3.9.23.tar.xz...</span><br><span class="line"><span class="meta prompt_">-&gt; </span><span class="language-bash">https://www.python.org/ftp/python/3.9.23/Python-3.9.23.tar.xz</span></span><br><span class="line">Installing Python-3.9.23...</span><br><span class="line">python-build: use tcl-tk from homebrew</span><br><span class="line">python-build: use readline from homebrew</span><br><span class="line">python-build: use ncurses from homebrew</span><br><span class="line">python-build: use zlib from xcode sdk</span><br><span class="line"></span><br><span class="line">BUILD FAILED (OS X 15.7 using python-build 2.6.8)</span><br><span class="line"></span><br><span class="line">Inspect or clean up the working tree at /var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/python-build.20251001200658.15776</span><br><span class="line">Results logged to /var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/python-build.20251001200658.15776.log</span><br><span class="line"></span><br><span class="line">Last 10 log lines:</span><br><span class="line">creating Modules/Setup.local</span><br><span class="line">creating Makefile</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">If you want a release build with all stable optimizations active (PGO, etc),</span><br><span class="line">please run ./configure --enable-optimizations</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">make: *** No rule to make target `pyconfig.h&#x27;, needed by `Programs/python.o&#x27;.  Stop.</span><br><span class="line">make: *** Waiting for unfinished jobs....</span><br></pre></td></tr></table></figure><p>这里的错误是：No rule to make target <code>pyconfig.h&#39;, needed by </code>Programs&#x2F;python.o’。</p><p>然后在 pyenv 项目下和 StackOverflow 找了很多资料做了一些尝试，我做过哪些尝试？</p><ul><li><p>Wiki，这里的很多细节还是值得一看：</p><ol><li>参考<a class="link"   href="https://github.com/pyenv/pyenv/wiki#suggested-build-environment" >https://github.com/pyenv/pyenv/wiki#suggested-build-environment<i class="fas fa-external-link-alt"></i></a>，每一项都做了检查和尝试，均遇到同样的问题。</li><li>参考<a class="link"   href="https://github.com/pyenv/pyenv/wiki/Common-build-problems" >https://github.com/pyenv/pyenv/wiki/Common-build-problems<i class="fas fa-external-link-alt"></i></a>，每一项都做了检查和尝试，均遇到同样的问题。</li></ol></li><li><p>StackOverflow：</p><ol><li><a class="link"   href="https://stackoverflow.com/questions/51551557/pyenv-build-failed-installing-python-on-macos" >https://stackoverflow.com/questions/51551557/pyenv-build-failed-installing-python-on-macos<i class="fas fa-external-link-alt"></i></a></li></ol></li><li><p>Issues：</p><ol><li><a class="link"   href="https://github.com/pyenv/pyenv/issues/1354" >https://github.com/pyenv/pyenv/issues/1354<i class="fas fa-external-link-alt"></i></a>，问题相同，但是我没有理解答案，忽略答案提到的一个命令，没有做检查。</li><li><a class="link"   href="https://github.com/pyenv/pyenv/issues/3310" >https://github.com/pyenv/pyenv/issues/3310<i class="fas fa-external-link-alt"></i></a>，问题类似，但没有解决。</li><li><a class="link"   href="https://github.com/pyenv/pyenv/issues/3062" >https://github.com/pyenv/pyenv/issues/3062<i class="fas fa-external-link-alt"></i></a>，在这里找到了答案，但历程有点揪心。</li></ol></li></ul><p>最终是 Issues 中的 1 和 3 解决了我的问题，我接下来说下原因。</p><p>最开始，我是在 Issues 中找答案的，所以一直在看 Issues，但是其实问题<code>#1354</code>中已经讲到了，但是没有引起我的重视：</p><blockquote><p>Problem solved. I had overridden the diff command in my .bash_profile. Uncommenting those lines make the .&#x2F;configure step detect that pyconfig.h needed to be created!</p></blockquote><p>我在<code>~/.zshrc</code>、<code>/etc/zshrc</code>、<code>/etc/zprofile</code>、<code>/etc/paths</code>中都找了有没有什么配置覆盖了 diff 命令，但是没有找到。</p><p>接下来我就有点郁闷了，就继续尝试了 Wiki、StackOverflow 中提到的一些方案，浪费了个把小时的时间吧。</p><p>就在刚辞，然后回到问题<code>#3062</code>中再过一遍，发现成员提到：</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+ eval &#x27;$AWK -f &quot;$ac_tmp/defines.awk&quot;&#x27; &#x27; &#x27;\&#x27;&#x27;pyconfig.h.in&#x27;\&#x27;&#x27;&#x27;</span><br><span class="line">++ awk -f ./confxRYd6w/defines.awk pyconfig.h.in</span><br><span class="line">+ diff pyconfig.h ./confxRYd6w/config.h</span><br></pre></td></tr></table></figure><p>This is where it goes wrong. Need to closely examine what happens at this moment: what is your and , if the file and look normal, what the command outputs, what code the returns.<code>awk</code> <code>diff</code> <code>.awk</code> <code>pyconfig.h.in</code> <code>awk</code> <code>diff</code><br>Perhaps we could arrange a remote access session for me to examine that on your system? Contact me on the e-mail in my profile if you’re interested.</p></blockquote><p>然后我立刻在页面上搜索<code>eval &#39;$AWK -f</code>，发现出问题的人日志里确实有：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">+ printf &#x27;%s\n&#x27; &#x27;/* pyconfig.h.  Generated from pyconfig.h.in by configure.  */&#x27;</span><br><span class="line">+ eval &#x27;$AWK -f &quot;$ac_tmp/defines.awk&quot;&#x27; &#x27; &#x27;\&#x27;&#x27;pyconfig.h.in&#x27;\&#x27;&#x27;&#x27;</span><br><span class="line">++ awk -f ./confxRYd6w/defines.awk pyconfig.h.in</span><br><span class="line">+ diff pyconfig.h ./confxRYd6w/config.h</span><br><span class="line">+ printf &#x27;%s\n&#x27; &#x27;config.status:1854: pyconfig.h is unchanged&#x27;</span><br><span class="line">+ printf &#x27;%s\n&#x27; &#x27;config.status: pyconfig.h is unchanged&#x27;</span><br><span class="line">config.status: pyconfig.h is unchanged</span><br></pre></td></tr></table></figure><p>看到关键词<code>unchanged</code>，我又想到了 diff 命令，赶紧根据 log 提示路径打开详细日志&#x2F;var&#x2F;folders&#x2F;wh&#x2F;33kqcmn975z7bsfmvmlrsy880000gp&#x2F;T&#x2F;python-build.20251001200658.15776.log：</p><p>搜索<code>unchanged</code>果然看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">config.status: creating pyconfig.h</span><br><span class="line">config.status: pyconfig.h is unchanged</span><br><span class="line">creating Modules/Setup.local</span><br><span class="line">creating Makefile</span><br></pre></td></tr></table></figure><p>OK，基本上确认就是 diff 命令引起的，于是我马上去翻了<code>~/.zshrc</code>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Harmony</span></span><br><span class="line">export HM_CMD_LINE_TOOLS=$HOME/Developer/Harmony/command-line-tools</span><br><span class="line">export HM_SDK_HOME=$HM_CMD_LINE_TOOLS/sdk/default</span><br><span class="line">export PATH=$HM_CMD_LINE_TOOLS/bin:$HM_SDK_HOME/openharmony/toolchains:$PATH</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> HDC_SERVER_PORT=7035</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">launchctl setenv HDC_SERVER_PORT <span class="variable">$HDC_SERVER_PORT</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> HDC_SERVER_PORT</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Android</span></span><br><span class="line">export ANDROID_HOME=$HOME/Library/Android/sdk</span><br><span class="line">export PATH=$ANDROID_HOME/platform-tools:$PATH</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> ANDROID_NDK_HOME=<span class="variable">$ANDROID_HOME</span>/ndk/21.4.7075529</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> TOOLCHAIN=<span class="variable">$ANDROID_NDK_HOME</span>/toolchains/llvm/prebuilt/darwin-x86_64</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> PATH=<span class="variable">$ANDROID_HOME</span>/platform-tools2:<span class="variable">$TOOLCHAIN</span>:<span class="variable">$PATH</span></span></span><br></pre></td></tr></table></figure><p>我关注到了这一段，Android 的环境我用了很多年了，期间也安装过 pyenv，没有出过问题，Harmony 的环境是去年安装的，但是 pyenv 和 pyenv 安装的 Python 是在这之前，我感觉问题就在这里。</p><p>于是我查了下 diff 命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">which</span> diff</span></span><br><span class="line">~/Developer/Harmony/command-line-tools/sdk/default/openharmony/toolchains/diff</span><br></pre></td></tr></table></figure><p>王德发？果然是你小子，在这几秒之前我甚至打开了 Harmony 目录都没看见（疏忽了，太相信华为了）。</p><p>然后注从 PATH 中移除<code>$HM_SDK_HOME/openharmony/toolchains</code>后就恢复了。</p><hr><p>贴一下完整的日志：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br><span class="line">633</span><br><span class="line">634</span><br><span class="line">635</span><br><span class="line">636</span><br><span class="line">637</span><br><span class="line">638</span><br><span class="line">639</span><br><span class="line">640</span><br><span class="line">641</span><br><span class="line">642</span><br><span class="line">643</span><br><span class="line">644</span><br><span class="line">645</span><br><span class="line">646</span><br><span class="line">647</span><br><span class="line">648</span><br><span class="line">649</span><br><span class="line">650</span><br><span class="line">651</span><br><span class="line">652</span><br><span class="line">653</span><br><span class="line">654</span><br><span class="line">655</span><br><span class="line">656</span><br><span class="line">657</span><br><span class="line">658</span><br><span class="line">659</span><br><span class="line">660</span><br><span class="line">661</span><br><span class="line">662</span><br><span class="line">663</span><br><span class="line">664</span><br><span class="line">665</span><br><span class="line">666</span><br><span class="line">667</span><br><span class="line">668</span><br><span class="line">669</span><br><span class="line">670</span><br><span class="line">671</span><br><span class="line">672</span><br><span class="line">673</span><br><span class="line">674</span><br><span class="line">675</span><br><span class="line">676</span><br><span class="line">677</span><br><span class="line">678</span><br><span class="line">679</span><br><span class="line">680</span><br><span class="line">681</span><br><span class="line">682</span><br><span class="line">683</span><br><span class="line">684</span><br><span class="line">685</span><br><span class="line">686</span><br><span class="line">687</span><br><span class="line">688</span><br><span class="line">689</span><br><span class="line">690</span><br><span class="line">691</span><br><span class="line">692</span><br><span class="line">693</span><br><span class="line">694</span><br><span class="line">695</span><br><span class="line">696</span><br><span class="line">697</span><br><span class="line">698</span><br><span class="line">699</span><br><span class="line">700</span><br><span class="line">701</span><br><span class="line">702</span><br><span class="line">703</span><br><span class="line">704</span><br><span class="line">705</span><br><span class="line">706</span><br><span class="line">707</span><br><span class="line">708</span><br><span class="line">709</span><br><span class="line">710</span><br><span class="line">711</span><br><span class="line">712</span><br><span class="line">713</span><br><span class="line">714</span><br><span class="line">715</span><br><span class="line">716</span><br><span class="line">717</span><br><span class="line">718</span><br><span class="line">719</span><br><span class="line">720</span><br><span class="line">721</span><br><span class="line">722</span><br><span class="line">723</span><br><span class="line">724</span><br><span class="line">725</span><br><span class="line">726</span><br><span class="line">727</span><br><span class="line">728</span><br><span class="line">729</span><br><span class="line">730</span><br><span class="line">731</span><br><span class="line">732</span><br><span class="line">733</span><br><span class="line">734</span><br><span class="line">735</span><br><span class="line">736</span><br><span class="line">737</span><br><span class="line">738</span><br><span class="line">739</span><br><span class="line">740</span><br><span class="line">741</span><br><span class="line">742</span><br><span class="line">743</span><br><span class="line">744</span><br><span class="line">745</span><br><span class="line">746</span><br><span class="line">747</span><br><span class="line">748</span><br><span class="line">749</span><br><span class="line">750</span><br><span class="line">751</span><br><span class="line">752</span><br><span class="line">753</span><br><span class="line">754</span><br><span class="line">755</span><br><span class="line">756</span><br><span class="line">757</span><br><span class="line">758</span><br><span class="line">759</span><br><span class="line">760</span><br><span class="line">761</span><br><span class="line">762</span><br><span class="line">763</span><br><span class="line">764</span><br></pre></td><td class="code"><pre><span class="line">/var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/python-build.20251001202802.30502 ~/Workspace/ModelScope/IndexTTS-2-Demo</span><br><span class="line">/var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/python-build.20251001202802.30502/Python-3.9.23 /var/folders/wh/33kqcmn975z7bsfmvmlrsy880000gp/T/python-build.20251001202802.30502 ~/Workspace/ModelScope/IndexTTS-2-Demo</span><br><span class="line">checking build system type... aarch64-apple-darwin24.6.0</span><br><span class="line">checking host system type... aarch64-apple-darwin24.6.0</span><br><span class="line">checking for python3.9... no</span><br><span class="line">checking for python3... python3</span><br><span class="line">checking for --enable-universalsdk... no</span><br><span class="line">checking for --with-universal-archs... no</span><br><span class="line">checking MACHDEP... &quot;darwin&quot;</span><br><span class="line">checking for gcc... clang</span><br><span class="line">checking whether the C compiler works... yes</span><br><span class="line">checking for C compiler default output file name... a.out</span><br><span class="line">checking for suffix of executables...</span><br><span class="line">checking whether we are cross compiling... no</span><br><span class="line">checking for suffix of object files... o</span><br><span class="line">checking whether we are using the GNU C compiler... yes</span><br><span class="line">checking whether clang accepts -g... yes</span><br><span class="line">checking for clang option to accept ISO C89... none needed</span><br><span class="line">checking how to run the C preprocessor... clang -E</span><br><span class="line">checking for grep that handles long lines and -e... /usr/bin/grep</span><br><span class="line">checking for a sed that does not truncate output... /usr/bin/sed</span><br><span class="line">checking for --with-cxx-main=&lt;compiler&gt;... no</span><br><span class="line">checking for clang++... no</span><br><span class="line">configure:</span><br><span class="line"></span><br><span class="line">  By default, distutils will build C++ extension modules with &quot;clang++&quot;.</span><br><span class="line">  If this is not intended, then set CXX on the configure command line.</span><br><span class="line"></span><br><span class="line">checking for the platform triplet based on compiler characteristics... darwin</span><br><span class="line">checking for multiarch...</span><br><span class="line">checking for -Wl,--no-as-needed... no</span><br><span class="line">checking for egrep... /usr/bin/grep -E</span><br><span class="line">checking for ANSI C header files... yes</span><br><span class="line">checking for sys/types.h... yes</span><br><span class="line">checking for sys/stat.h... yes</span><br><span class="line">checking for stdlib.h... yes</span><br><span class="line">checking for string.h... yes</span><br><span class="line">checking for memory.h... yes</span><br><span class="line">checking for strings.h... yes</span><br><span class="line">checking for inttypes.h... yes</span><br><span class="line">checking for stdint.h... yes</span><br><span class="line">checking for unistd.h... yes</span><br><span class="line">checking minix/config.h usability... no</span><br><span class="line">checking minix/config.h presence... no</span><br><span class="line">checking for minix/config.h... no</span><br><span class="line">checking whether it is safe to define __EXTENSIONS__... yes</span><br><span class="line">checking for the Android API level... not Android</span><br><span class="line">checking for --with-suffix...</span><br><span class="line">checking for case-insensitive build directory... yes</span><br><span class="line">checking LIBRARY... libpython$(VERSION)$(ABIFLAGS).a</span><br><span class="line">checking LINKCC... $(PURIFY) $(MAINCC)</span><br><span class="line">checking EXPORTSYMS...</span><br><span class="line">checking for GNU ld... no</span><br><span class="line">checking for --enable-shared... yes</span><br><span class="line">checking for --enable-profiling... no</span><br><span class="line">checking LDLIBRARY... libpython$(LDVERSION).dylib</span><br><span class="line">checking for ar... ar</span><br><span class="line">checking for readelf... no</span><br><span class="line">checking for a BSD-compatible install... /usr/bin/install -c</span><br><span class="line">checking for a thread-safe mkdir -p... ./install-sh -c -d</span><br><span class="line">checking for --with-pydebug... no</span><br><span class="line">checking for --with-trace-refs... no</span><br><span class="line">checking for --with-assertions... no</span><br><span class="line">checking for --enable-optimizations... no</span><br><span class="line">checking PROFILE_TASK... -m test --pgo</span><br><span class="line">checking for --with-lto... no</span><br><span class="line">checking for llvm-profdata... no</span><br><span class="line">configure: llvm-profdata found via xcrun: /usr/bin/xcrun llvm-profdata</span><br><span class="line">checking for -Wextra... yes</span><br><span class="line">checking whether clang accepts and needs -fno-strict-aliasing... no</span><br><span class="line">checking if we can turn off clang unused result warning... yes</span><br><span class="line">checking if we can turn off clang unused parameter warning... yes</span><br><span class="line">checking if we can turn off clang missing field initializers warning... yes</span><br><span class="line">checking if we can turn on clang mixed sign comparison warning... yes</span><br><span class="line">checking if we can turn on clang unreachable code warning... yes</span><br><span class="line">checking if we can turn on clang strict-prototypes warning... no</span><br><span class="line">checking if we can make implicit function declaration an error in clang... yes</span><br><span class="line">checking if we can use visibility in clang... yes</span><br><span class="line">checking which MACOSX_DEPLOYMENT_TARGET to use... 15.7</span><br><span class="line">checking if specified universal architectures work... yes</span><br><span class="line">checking whether pthreads are available without options... yes</span><br><span class="line">checking whether clang++ also accepts flags for thread support... no</span><br><span class="line">checking for ANSI C header files... (cached) yes</span><br><span class="line">checking asm/types.h usability... no</span><br><span class="line">checking asm/types.h presence... no</span><br><span class="line">checking for asm/types.h... no</span><br><span class="line">checking crypt.h usability... no</span><br><span class="line">checking crypt.h presence... no</span><br><span class="line">checking for crypt.h... no</span><br><span class="line">checking conio.h usability... no</span><br><span class="line">checking conio.h presence... no</span><br><span class="line">checking for conio.h... no</span><br><span class="line">checking direct.h usability... no</span><br><span class="line">checking direct.h presence... no</span><br><span class="line">checking for direct.h... no</span><br><span class="line">checking dlfcn.h usability... yes</span><br><span class="line">checking dlfcn.h presence... yes</span><br><span class="line">checking for dlfcn.h... yes</span><br><span class="line">checking errno.h usability... yes</span><br><span class="line">checking errno.h presence... yes</span><br><span class="line">checking for errno.h... yes</span><br><span class="line">checking fcntl.h usability... yes</span><br><span class="line">checking fcntl.h presence... yes</span><br><span class="line">checking for fcntl.h... yes</span><br><span class="line">checking grp.h usability... yes</span><br><span class="line">checking grp.h presence... yes</span><br><span class="line">checking for grp.h... yes</span><br><span class="line">checking ieeefp.h usability... no</span><br><span class="line">checking ieeefp.h presence... no</span><br><span class="line">checking for ieeefp.h... no</span><br><span class="line">checking io.h usability... no</span><br><span class="line">checking io.h presence... no</span><br><span class="line">checking for io.h... no</span><br><span class="line">checking langinfo.h usability... yes</span><br><span class="line">checking langinfo.h presence... yes</span><br><span class="line">checking for langinfo.h... yes</span><br><span class="line">checking libintl.h usability... no</span><br><span class="line">checking libintl.h presence... no</span><br><span class="line">checking for libintl.h... no</span><br><span class="line">checking process.h usability... no</span><br><span class="line">checking process.h presence... no</span><br><span class="line">checking for process.h... no</span><br><span class="line">checking pthread.h usability... yes</span><br><span class="line">checking pthread.h presence... yes</span><br><span class="line">checking for pthread.h... yes</span><br><span class="line">checking sched.h usability... yes</span><br><span class="line">checking sched.h presence... yes</span><br><span class="line">checking for sched.h... yes</span><br><span class="line">checking shadow.h usability... no</span><br><span class="line">checking shadow.h presence... no</span><br><span class="line">checking for shadow.h... no</span><br><span class="line">checking signal.h usability... yes</span><br><span class="line">checking signal.h presence... yes</span><br><span class="line">checking for signal.h... yes</span><br><span class="line">checking stropts.h usability... no</span><br><span class="line">checking stropts.h presence... no</span><br><span class="line">checking for stropts.h... no</span><br><span class="line">checking termios.h usability... yes</span><br><span class="line">checking termios.h presence... yes</span><br><span class="line">checking for termios.h... yes</span><br><span class="line">checking utime.h usability... yes</span><br><span class="line">checking utime.h presence... yes</span><br><span class="line">checking for utime.h... yes</span><br><span class="line">checking poll.h usability... yes</span><br><span class="line">checking poll.h presence... yes</span><br><span class="line">checking for poll.h... yes</span><br><span class="line">checking sys/devpoll.h usability... no</span><br><span class="line">checking sys/devpoll.h presence... no</span><br><span class="line">checking for sys/devpoll.h... no</span><br><span class="line">checking sys/epoll.h usability... no</span><br><span class="line">checking sys/epoll.h presence... no</span><br><span class="line">checking for sys/epoll.h... no</span><br><span class="line">checking sys/poll.h usability... yes</span><br><span class="line">checking sys/poll.h presence... yes</span><br><span class="line">checking for sys/poll.h... yes</span><br><span class="line">checking sys/audioio.h usability... no</span><br><span class="line">checking sys/audioio.h presence... no</span><br><span class="line">checking for sys/audioio.h... no</span><br><span class="line">checking sys/xattr.h usability... yes</span><br><span class="line">checking sys/xattr.h presence... yes</span><br><span class="line">checking for sys/xattr.h... yes</span><br><span class="line">checking sys/bsdtty.h usability... no</span><br><span class="line">checking sys/bsdtty.h presence... no</span><br><span class="line">checking for sys/bsdtty.h... no</span><br><span class="line">checking sys/event.h usability... yes</span><br><span class="line">checking sys/event.h presence... yes</span><br><span class="line">checking for sys/event.h... yes</span><br><span class="line">checking sys/file.h usability... yes</span><br><span class="line">checking sys/file.h presence... yes</span><br><span class="line">checking for sys/file.h... yes</span><br><span class="line">checking sys/ioctl.h usability... yes</span><br><span class="line">checking sys/ioctl.h presence... yes</span><br><span class="line">checking for sys/ioctl.h... yes</span><br><span class="line">checking sys/kern_control.h usability... yes</span><br><span class="line">checking sys/kern_control.h presence... yes</span><br><span class="line">checking for sys/kern_control.h... yes</span><br><span class="line">checking sys/loadavg.h usability... no</span><br><span class="line">checking sys/loadavg.h presence... no</span><br><span class="line">checking for sys/loadavg.h... no</span><br><span class="line">checking sys/lock.h usability... yes</span><br><span class="line">checking sys/lock.h presence... yes</span><br><span class="line">checking for sys/lock.h... yes</span><br><span class="line">checking sys/mkdev.h usability... no</span><br><span class="line">checking sys/mkdev.h presence... no</span><br><span class="line">checking for sys/mkdev.h... no</span><br><span class="line">checking sys/modem.h usability... no</span><br><span class="line">checking sys/modem.h presence... no</span><br><span class="line">checking for sys/modem.h... no</span><br><span class="line">checking sys/param.h usability... yes</span><br><span class="line">checking sys/param.h presence... yes</span><br><span class="line">checking for sys/param.h... yes</span><br><span class="line">checking sys/random.h usability... yes</span><br><span class="line">checking sys/random.h presence... yes</span><br><span class="line">checking for sys/random.h... yes</span><br><span class="line">checking sys/select.h usability... yes</span><br><span class="line">checking sys/select.h presence... yes</span><br><span class="line">checking for sys/select.h... yes</span><br><span class="line">checking sys/sendfile.h usability... no</span><br><span class="line">checking sys/sendfile.h presence... no</span><br><span class="line">checking for sys/sendfile.h... no</span><br><span class="line">checking sys/socket.h usability... yes</span><br><span class="line">checking sys/socket.h presence... yes</span><br><span class="line">checking for sys/socket.h... yes</span><br><span class="line">checking sys/statvfs.h usability... yes</span><br><span class="line">checking sys/statvfs.h presence... yes</span><br><span class="line">checking for sys/statvfs.h... yes</span><br><span class="line">checking for sys/stat.h... (cached) yes</span><br><span class="line">checking sys/syscall.h usability... yes</span><br><span class="line">checking sys/syscall.h presence... yes</span><br><span class="line">checking for sys/syscall.h... yes</span><br><span class="line">checking sys/sys_domain.h usability... yes</span><br><span class="line">checking sys/sys_domain.h presence... yes</span><br><span class="line">checking for sys/sys_domain.h... yes</span><br><span class="line">checking sys/termio.h usability... no</span><br><span class="line">checking sys/termio.h presence... no</span><br><span class="line">checking for sys/termio.h... no</span><br><span class="line">checking sys/time.h usability... yes</span><br><span class="line">checking sys/time.h presence... yes</span><br><span class="line">checking for sys/time.h... yes</span><br><span class="line">checking sys/times.h usability... yes</span><br><span class="line">checking sys/times.h presence... yes</span><br><span class="line">checking for sys/times.h... yes</span><br><span class="line">checking for sys/types.h... (cached) yes</span><br><span class="line">checking sys/uio.h usability... yes</span><br><span class="line">checking sys/uio.h presence... yes</span><br><span class="line">checking for sys/uio.h... yes</span><br><span class="line">checking sys/un.h usability... yes</span><br><span class="line">checking sys/un.h presence... yes</span><br><span class="line">checking for sys/un.h... yes</span><br><span class="line">checking sys/utsname.h usability... yes</span><br><span class="line">checking sys/utsname.h presence... yes</span><br><span class="line">checking for sys/utsname.h... yes</span><br><span class="line">checking sys/wait.h usability... yes</span><br><span class="line">checking sys/wait.h presence... yes</span><br><span class="line">checking for sys/wait.h... yes</span><br><span class="line">checking pty.h usability... no</span><br><span class="line">checking pty.h presence... no</span><br><span class="line">checking for pty.h... no</span><br><span class="line">checking libutil.h usability... no</span><br><span class="line">checking libutil.h presence... no</span><br><span class="line">checking for libutil.h... no</span><br><span class="line">checking sys/resource.h usability... yes</span><br><span class="line">checking sys/resource.h presence... yes</span><br><span class="line">checking for sys/resource.h... yes</span><br><span class="line">checking netpacket/packet.h usability... no</span><br><span class="line">checking netpacket/packet.h presence... no</span><br><span class="line">checking for netpacket/packet.h... no</span><br><span class="line">checking sysexits.h usability... yes</span><br><span class="line">checking sysexits.h presence... yes</span><br><span class="line">checking for sysexits.h... yes</span><br><span class="line">checking bluetooth.h usability... no</span><br><span class="line">checking bluetooth.h presence... no</span><br><span class="line">checking for bluetooth.h... no</span><br><span class="line">checking linux/tipc.h usability... no</span><br><span class="line">checking linux/tipc.h presence... no</span><br><span class="line">checking for linux/tipc.h... no</span><br><span class="line">checking linux/random.h usability... no</span><br><span class="line">checking linux/random.h presence... no</span><br><span class="line">checking for linux/random.h... no</span><br><span class="line">checking spawn.h usability... yes</span><br><span class="line">checking spawn.h presence... yes</span><br><span class="line">checking for spawn.h... yes</span><br><span class="line">checking util.h usability... yes</span><br><span class="line">checking util.h presence... yes</span><br><span class="line">checking for util.h... yes</span><br><span class="line">checking alloca.h usability... yes</span><br><span class="line">checking alloca.h presence... yes</span><br><span class="line">checking for alloca.h... yes</span><br><span class="line">checking endian.h usability... no</span><br><span class="line">checking endian.h presence... no</span><br><span class="line">checking for endian.h... no</span><br><span class="line">checking sys/endian.h usability... yes</span><br><span class="line">checking sys/endian.h presence... yes</span><br><span class="line">checking for sys/endian.h... yes</span><br><span class="line">checking sys/sysmacros.h usability... no</span><br><span class="line">checking sys/sysmacros.h presence... no</span><br><span class="line">checking for sys/sysmacros.h... no</span><br><span class="line">checking linux/auxvec.h usability... no</span><br><span class="line">checking linux/auxvec.h presence... no</span><br><span class="line">checking for linux/auxvec.h... no</span><br><span class="line">checking sys/auxv.h usability... no</span><br><span class="line">checking sys/auxv.h presence... no</span><br><span class="line">checking for sys/auxv.h... no</span><br><span class="line">checking linux/memfd.h usability... no</span><br><span class="line">checking linux/memfd.h presence... no</span><br><span class="line">checking for linux/memfd.h... no</span><br><span class="line">checking linux/wait.h usability... no</span><br><span class="line">checking linux/wait.h presence... no</span><br><span class="line">checking for linux/wait.h... no</span><br><span class="line">checking sys/memfd.h usability... no</span><br><span class="line">checking sys/memfd.h presence... no</span><br><span class="line">checking for sys/memfd.h... no</span><br><span class="line">checking sys/mman.h usability... yes</span><br><span class="line">checking sys/mman.h presence... yes</span><br><span class="line">checking for sys/mman.h... yes</span><br><span class="line">checking for dirent.h that defines DIR... yes</span><br><span class="line">checking for library containing opendir... none required</span><br><span class="line">checking whether sys/types.h defines makedev... yes</span><br><span class="line">checking bluetooth/bluetooth.h usability... no</span><br><span class="line">checking bluetooth/bluetooth.h presence... no</span><br><span class="line">checking for bluetooth/bluetooth.h... no</span><br><span class="line">checking for net/if.h... yes</span><br><span class="line">checking for linux/netlink.h... no</span><br><span class="line">checking for linux/qrtr.h... no</span><br><span class="line">checking for linux/vm_sockets.h... no</span><br><span class="line">checking for linux/can.h... no</span><br><span class="line">checking for linux/can/bcm.h... no</span><br><span class="line">checking for linux/can/j1939.h... no</span><br><span class="line">checking for linux/can/raw.h... no</span><br><span class="line">checking for clock_t in time.h... yes</span><br><span class="line">checking for makedev... yes</span><br><span class="line">checking for le64toh... yes</span><br><span class="line">checking for mode_t... yes</span><br><span class="line">checking for off_t... yes</span><br><span class="line">checking for pid_t... yes</span><br><span class="line">checking for size_t... yes</span><br><span class="line">checking for uid_t in sys/types.h... yes</span><br><span class="line">checking for ssize_t... yes</span><br><span class="line">checking for __uint128_t... yes</span><br><span class="line">checking size of int... 4</span><br><span class="line">checking size of long... 8</span><br><span class="line">checking size of long long... 8</span><br><span class="line">checking size of void *... 8</span><br><span class="line">checking size of short... 2</span><br><span class="line">checking size of float... 4</span><br><span class="line">checking size of double... 8</span><br><span class="line">checking size of fpos_t... 8</span><br><span class="line">checking size of size_t... 8</span><br><span class="line">checking size of pid_t... 4</span><br><span class="line">checking size of uintptr_t... 8</span><br><span class="line">checking for long double... yes</span><br><span class="line">checking size of long double... 8</span><br><span class="line">checking size of _Bool... 1</span><br><span class="line">checking size of off_t... 8</span><br><span class="line">checking whether to enable large file support... no</span><br><span class="line">checking size of time_t... 8</span><br><span class="line">checking for pthread_t... yes</span><br><span class="line">checking size of pthread_t... 8</span><br><span class="line">checking size of pthread_key_t... 8</span><br><span class="line">checking whether pthread_key_t is compatible with int... no</span><br><span class="line">checking for --enable-framework... no</span><br><span class="line">checking for dyld... always on for Darwin</span><br><span class="line">checking the extension of shared libraries... .so</span><br><span class="line">checking LDSHARED... $(CC) -bundle -undefined dynamic_lookup</span><br><span class="line">checking CCSHARED...</span><br><span class="line">checking LINKFORSHARED... -Wl,-stack_size,1000000  -framework CoreFoundation</span><br><span class="line">checking CFLAGSFORSHARED... $(CCSHARED)</span><br><span class="line">checking SHLIBS... $(LIBS)</span><br><span class="line">checking for sendfile in -lsendfile... no</span><br><span class="line">checking for dlopen in -ldl... yes</span><br><span class="line">checking for shl_load in -ldld... no</span><br><span class="line">checking uuid/uuid.h usability... yes</span><br><span class="line">checking uuid/uuid.h presence... yes</span><br><span class="line">checking for uuid/uuid.h... yes</span><br><span class="line">checking uuid.h usability... no</span><br><span class="line">checking uuid.h presence... no</span><br><span class="line">checking for uuid.h... no</span><br><span class="line">checking for uuid_generate_time_safe... no</span><br><span class="line">checking for uuid_generate_time in -luuid... no</span><br><span class="line">checking for uuid_create... no</span><br><span class="line">checking for uuid_enc_be... no</span><br><span class="line">checking for library containing sem_init... none required</span><br><span class="line">checking for textdomain in -lintl... no</span><br><span class="line">checking aligned memory access is required... no</span><br><span class="line">checking for --with-hash-algorithm... default</span><br><span class="line">checking for --with-tzpath... &quot;/usr/share/zoneinfo:/usr/lib/zoneinfo:/usr/share/lib/zoneinfo:/etc/zoneinfo&quot;</span><br><span class="line">checking for --with-address-sanitizer... no</span><br><span class="line">checking for --with-memory-sanitizer... no</span><br><span class="line">checking for --with-undefined-behavior-sanitizer... no</span><br><span class="line">checking for t_open in -lnsl... no</span><br><span class="line">checking for socket in -lsocket... no</span><br><span class="line">checking for --with-libs... no</span><br><span class="line">checking for pkg-config... /opt/homebrew/bin/pkg-config</span><br><span class="line">checking pkg-config is at least version 0.9.0... yes</span><br><span class="line">checking for --with-system-expat... no</span><br><span class="line">checking for --with-system-ffi... no</span><br><span class="line">checking for --with-system-libmpdec... no</span><br><span class="line">checking for --with-decimal-contextvar... yes</span><br><span class="line">checking for --enable-loadable-sqlite-extensions... no</span><br><span class="line">checking for --with-tcltk-includes... -I/opt/homebrew/opt/tcl-tk/include/tcl-tk</span><br><span class="line">checking for --with-tcltk-libs... -L/opt/homebrew/opt/tcl-tk/lib -ltcl9.0 -ltk9.0</span><br><span class="line">checking for --with-dbmliborder...</span><br><span class="line">checking if PTHREAD_SCOPE_SYSTEM is supported... no</span><br><span class="line">checking for pthread_sigmask... yes</span><br><span class="line">checking for pthread_getcpuclockid... no</span><br><span class="line">checking if --enable-ipv6 is specified... yes</span><br><span class="line">checking if RFC2553 API is available... yes</span><br><span class="line">checking ipv6 stack type... kame</span><br><span class="line">using libc</span><br><span class="line">checking for CAN_RAW_FD_FRAMES... no</span><br><span class="line">checking for CAN_RAW_JOIN_FILTERS... no</span><br><span class="line">checking for --with-doc-strings... yes</span><br><span class="line">checking for --with-pymalloc... yes</span><br><span class="line">checking for --with-c-locale-coercion... yes</span><br><span class="line">checking for --with-valgrind... no</span><br><span class="line">checking for --with-dtrace... no</span><br><span class="line">checking for dlopen... yes</span><br><span class="line">checking DYNLOADFILE... dynload_shlib.o</span><br><span class="line">checking MACHDEP_OBJS... none</span><br><span class="line">checking for alarm... yes</span><br><span class="line">checking for accept4... no</span><br><span class="line">checking for setitimer... yes</span><br><span class="line">checking for getitimer... yes</span><br><span class="line">checking for bind_textdomain_codeset... no</span><br><span class="line">checking for chown... yes</span><br><span class="line">checking for clock... yes</span><br><span class="line">checking for confstr... yes</span><br><span class="line">checking for copy_file_range... no</span><br><span class="line">checking for ctermid... yes</span><br><span class="line">checking for dup3... no</span><br><span class="line">checking for execv... yes</span><br><span class="line">checking for explicit_bzero... no</span><br><span class="line">checking for explicit_memset... no</span><br><span class="line">checking for faccessat... yes</span><br><span class="line">checking for fchmod... yes</span><br><span class="line">checking for fchmodat... yes</span><br><span class="line">checking for fchown... yes</span><br><span class="line">checking for fchownat... yes</span><br><span class="line">checking for fdwalk... no</span><br><span class="line">checking for fexecve... no</span><br><span class="line">checking for fdopendir... yes</span><br><span class="line">checking for fork... yes</span><br><span class="line">checking for fpathconf... yes</span><br><span class="line">checking for fstatat... yes</span><br><span class="line">checking for ftime... yes</span><br><span class="line">checking for ftruncate... yes</span><br><span class="line">checking for futimesat... no</span><br><span class="line">checking for futimens... yes</span><br><span class="line">checking for futimes... yes</span><br><span class="line">checking for gai_strerror... yes</span><br><span class="line">checking for getentropy... yes</span><br><span class="line">checking for getgrgid_r... yes</span><br><span class="line">checking for getgrnam_r... yes</span><br><span class="line">checking for getgrouplist... yes</span><br><span class="line">checking for getgroups... yes</span><br><span class="line">checking for getlogin... yes</span><br><span class="line">checking for getloadavg... yes</span><br><span class="line">checking for getpeername... yes</span><br><span class="line">checking for getpgid... yes</span><br><span class="line">checking for getpid... yes</span><br><span class="line">checking for getpriority... yes</span><br><span class="line">checking for getresuid... no</span><br><span class="line">checking for getresgid... no</span><br><span class="line">checking for getpwent... yes</span><br><span class="line">checking for getpwnam_r... yes</span><br><span class="line">checking for getpwuid_r... yes</span><br><span class="line">checking for getspnam... no</span><br><span class="line">checking for getspent... no</span><br><span class="line">checking for getsid... yes</span><br><span class="line">checking for getwd... yes</span><br><span class="line">checking for if_nameindex... yes</span><br><span class="line">checking for initgroups... yes</span><br><span class="line">checking for kill... yes</span><br><span class="line">checking for killpg... yes</span><br><span class="line">checking for lchown... yes</span><br><span class="line">checking for lockf... yes</span><br><span class="line">checking for linkat... yes</span><br><span class="line">checking for lstat... yes</span><br><span class="line">checking for lutimes... yes</span><br><span class="line">checking for mmap... yes</span><br><span class="line">checking for memrchr... no</span><br><span class="line">checking for mbrtowc... yes</span><br><span class="line">checking for mkdirat... yes</span><br><span class="line">checking for mkfifo... yes</span><br><span class="line">checking for madvise... yes</span><br><span class="line">checking for mkfifoat... yes</span><br><span class="line">checking for mknod... yes</span><br><span class="line">checking for mknodat... yes</span><br><span class="line">checking for mktime... yes</span><br><span class="line">checking for mremap... no</span><br><span class="line">checking for nice... yes</span><br><span class="line">checking for openat... yes</span><br><span class="line">checking for pathconf... yes</span><br><span class="line">checking for pause... yes</span><br><span class="line">checking for pipe2... no</span><br><span class="line">checking for plock... no</span><br><span class="line">checking for poll... yes</span><br><span class="line">checking for posix_fallocate... no</span><br><span class="line">checking for posix_fadvise... no</span><br><span class="line">checking for posix_spawn... yes</span><br><span class="line">checking for posix_spawnp... yes</span><br><span class="line">checking for pread... yes</span><br><span class="line">checking for preadv... yes</span><br><span class="line">checking for preadv2... no</span><br><span class="line">checking for pthread_condattr_setclock... no</span><br><span class="line">checking for pthread_init... no</span><br><span class="line">checking for pthread_kill... yes</span><br><span class="line">checking for pwrite... yes</span><br><span class="line">checking for pwritev... yes</span><br><span class="line">checking for pwritev2... no</span><br><span class="line">checking for readlink... yes</span><br><span class="line">checking for readlinkat... yes</span><br><span class="line">checking for readv... yes</span><br><span class="line">checking for realpath... yes</span><br><span class="line">checking for renameat... yes</span><br><span class="line">checking for sem_open... yes</span><br><span class="line">checking for sem_timedwait... no</span><br><span class="line">checking for sem_clockwait... no</span><br><span class="line">checking for sem_getvalue... yes</span><br><span class="line">checking for sem_unlink... yes</span><br><span class="line">checking for sendfile... yes</span><br><span class="line">checking for setegid... yes</span><br><span class="line">checking for seteuid... yes</span><br><span class="line">checking for setgid... yes</span><br><span class="line">checking for sethostname... yes</span><br><span class="line">checking for setlocale... yes</span><br><span class="line">checking for setregid... yes</span><br><span class="line">checking for setreuid... yes</span><br><span class="line">checking for setresuid... no</span><br><span class="line">checking for setresgid... no</span><br><span class="line">checking for setsid... yes</span><br><span class="line">checking for setpgid... yes</span><br><span class="line">checking for setpgrp... yes</span><br><span class="line">checking for setpriority... yes</span><br><span class="line">checking for setuid... yes</span><br><span class="line">checking for setvbuf... yes</span><br><span class="line">checking for sched_get_priority_max... yes</span><br><span class="line">checking for sched_setaffinity... no</span><br><span class="line">checking for sched_setscheduler... no</span><br><span class="line">checking for sched_setparam... no</span><br><span class="line">checking for sched_rr_get_interval... no</span><br><span class="line">checking for sigaction... yes</span><br><span class="line">checking for sigaltstack... yes</span><br><span class="line">checking for sigfillset... yes</span><br><span class="line">checking for siginterrupt... yes</span><br><span class="line">checking for sigpending... yes</span><br><span class="line">checking for sigrelse... yes</span><br><span class="line">checking for sigtimedwait... no</span><br><span class="line">checking for sigwait... yes</span><br><span class="line">checking for sigwaitinfo... no</span><br><span class="line">checking for snprintf... yes</span><br><span class="line">checking for strftime... yes</span><br><span class="line">checking for strlcpy... yes</span><br><span class="line">checking for strsignal... yes</span><br><span class="line">checking for symlinkat... yes</span><br><span class="line">checking for sync... yes</span><br><span class="line">checking for sysconf... yes</span><br><span class="line">checking for tcgetpgrp... yes</span><br><span class="line">checking for tcsetpgrp... yes</span><br><span class="line">checking for tempnam... yes</span><br><span class="line">checking for timegm... yes</span><br><span class="line">checking for times... yes</span><br><span class="line">checking for tmpfile... yes</span><br><span class="line">checking for tmpnam... yes</span><br><span class="line">checking for tmpnam_r... no</span><br><span class="line">checking for truncate... yes</span><br><span class="line">checking for uname... yes</span><br><span class="line">checking for unlinkat... yes</span><br><span class="line">checking for utimensat... yes</span><br><span class="line">checking for utimes... yes</span><br><span class="line">checking for waitid... yes</span><br><span class="line">checking for waitpid... yes</span><br><span class="line">checking for wait3... yes</span><br><span class="line">checking for wait4... yes</span><br><span class="line">checking for wcscoll... yes</span><br><span class="line">checking for wcsftime... yes</span><br><span class="line">checking for wcsxfrm... yes</span><br><span class="line">checking for wmemcmp... yes</span><br><span class="line">checking for writev... yes</span><br><span class="line">checking for _getpty... no</span><br><span class="line">checking for rtpSpawn... no</span><br><span class="line">checking for lchmod... yes</span><br><span class="line">checking whether dirfd is declared... yes</span><br><span class="line">checking for chroot... yes</span><br><span class="line">checking for link... yes</span><br><span class="line">checking for symlink... yes</span><br><span class="line">checking for fchdir... yes</span><br><span class="line">checking for fsync... yes</span><br><span class="line">checking for fdatasync... no</span><br><span class="line">checking for epoll... no</span><br><span class="line">checking for epoll_create1... no</span><br><span class="line">checking for kqueue... yes</span><br><span class="line">checking for prlimit... no</span><br><span class="line">checking for _dyld_shared_cache_contains_path... yes</span><br><span class="line">checking for memfd_create... no</span><br><span class="line">checking for ctermid_r... yes</span><br><span class="line">checking for flock declaration... yes</span><br><span class="line">checking for flock... yes</span><br><span class="line">checking for getpagesize... yes</span><br><span class="line">checking for broken unsetenv... no</span><br><span class="line">checking for true... true</span><br><span class="line">checking for inet_aton in -lc... yes</span><br><span class="line">checking for chflags... yes</span><br><span class="line">checking for lchflags... yes</span><br><span class="line">checking for inflateCopy in -lz... yes</span><br><span class="line">checking for hstrerror... yes</span><br><span class="line">checking for inet_aton... yes</span><br><span class="line">checking for inet_pton... yes</span><br><span class="line">checking for setgroups... yes</span><br><span class="line">checking for openpty... yes</span><br><span class="line">checking for forkpty... yes</span><br><span class="line">checking for fseek64... no</span><br><span class="line">checking for fseeko... yes</span><br><span class="line">checking for fstatvfs... yes</span><br><span class="line">checking for ftell64... no</span><br><span class="line">checking for ftello... yes</span><br><span class="line">checking for statvfs... yes</span><br><span class="line">checking for dup2... yes</span><br><span class="line">checking for strdup... yes</span><br><span class="line">checking for getpgrp... yes</span><br><span class="line">checking for setpgrp... (cached) yes</span><br><span class="line">checking for library containing crypt... none required</span><br><span class="line">checking for library containing crypt_r... no</span><br><span class="line">checking for crypt_r... no</span><br><span class="line">checking for clock_gettime... yes</span><br><span class="line">checking for clock_getres... yes</span><br><span class="line">checking for clock_settime... yes</span><br><span class="line">checking for major... yes</span><br><span class="line">checking for getaddrinfo... yes</span><br><span class="line">checking getaddrinfo bug... no</span><br><span class="line">checking for getnameinfo... yes</span><br><span class="line">checking whether time.h and sys/time.h may both be included... yes</span><br><span class="line">checking whether struct tm is in sys/time.h or time.h... time.h</span><br><span class="line">checking for struct tm.tm_zone... yes</span><br><span class="line">checking for struct stat.st_rdev... yes</span><br><span class="line">checking for struct stat.st_blksize... yes</span><br><span class="line">checking for struct stat.st_flags... yes</span><br><span class="line">checking for struct stat.st_gen... yes</span><br><span class="line">checking for struct stat.st_birthtime... yes</span><br><span class="line">checking for struct stat.st_blocks... yes</span><br><span class="line">checking for struct passwd.pw_gecos... yes</span><br><span class="line">checking for struct passwd.pw_passwd... yes</span><br><span class="line">checking for siginfo_t.si_band... yes</span><br><span class="line">checking for time.h that defines altzone... no</span><br><span class="line">checking whether sys/select.h and sys/time.h may both be included... yes</span><br><span class="line">checking for addrinfo... yes</span><br><span class="line">checking for sockaddr_storage... yes</span><br><span class="line">checking for sockaddr_alg... no</span><br><span class="line">checking for an ANSI C-conforming const... yes</span><br><span class="line">checking for working signed char... yes</span><br><span class="line">checking for prototypes... yes</span><br><span class="line">checking for variable length prototypes and stdarg.h... yes</span><br><span class="line">checking for socketpair... yes</span><br><span class="line">checking if sockaddr has sa_len member... yes</span><br><span class="line">checking for gethostbyname_r... no</span><br><span class="line">checking for gethostbyname... yes</span><br><span class="line">checking for __fpu_control... no</span><br><span class="line">checking for __fpu_control in -lieee... no</span><br><span class="line">checking for --with-libm=STRING... default LIBM=&quot;&quot;</span><br><span class="line">checking for --with-libc=STRING... default LIBC=&quot;&quot;</span><br><span class="line">checking for x64 gcc inline assembler... no</span><br><span class="line">checking whether float word ordering is bigendian... no</span><br><span class="line">checking whether we can use gcc inline assembler to get and set x87 control word... no</span><br><span class="line">checking whether we can use gcc inline assembler to get and set mc68881 fpcr... no</span><br><span class="line">checking for x87-style double rounding... no</span><br><span class="line">checking for acosh... yes</span><br><span class="line">checking for asinh... yes</span><br><span class="line">checking for atanh... yes</span><br><span class="line">checking for copysign... yes</span><br><span class="line">checking for erf... yes</span><br><span class="line">checking for erfc... yes</span><br><span class="line">checking for expm1... yes</span><br><span class="line">checking for finite... no</span><br><span class="line">checking for gamma... no</span><br><span class="line">checking for hypot... yes</span><br><span class="line">checking for lgamma... yes</span><br><span class="line">checking for log1p... yes</span><br><span class="line">checking for log2... yes</span><br><span class="line">checking for round... yes</span><br><span class="line">checking for tgamma... yes</span><br><span class="line">checking whether isinf is declared... yes</span><br><span class="line">checking whether isnan is declared... yes</span><br><span class="line">checking whether isfinite is declared... yes</span><br><span class="line">checking whether POSIX semaphores are enabled... yes</span><br><span class="line">checking for broken sem_getvalue... yes</span><br><span class="line">checking whether RTLD_LAZY is declared... yes</span><br><span class="line">checking whether RTLD_NOW is declared... yes</span><br><span class="line">checking whether RTLD_GLOBAL is declared... yes</span><br><span class="line">checking whether RTLD_LOCAL is declared... yes</span><br><span class="line">checking whether RTLD_NODELETE is declared... yes</span><br><span class="line">checking whether RTLD_NOLOAD is declared... yes</span><br><span class="line">checking whether RTLD_DEEPBIND is declared... no</span><br><span class="line">checking whether RTLD_MEMBER is declared... no</span><br><span class="line">checking digit size for Python&#x27;s longs... no value specified</span><br><span class="line">checking wchar.h usability... yes</span><br><span class="line">checking wchar.h presence... yes</span><br><span class="line">checking for wchar.h... yes</span><br><span class="line">checking size of wchar_t... 4</span><br><span class="line">checking for UCS-4 tcl... no</span><br><span class="line">checking whether wchar_t is signed... yes</span><br><span class="line">checking whether wchar_t is usable... no</span><br><span class="line">checking whether byte ordering is bigendian... no</span><br><span class="line">checking ABIFLAGS...</span><br><span class="line">checking SOABI... cpython-39-darwin</span><br><span class="line">checking LDVERSION... $(VERSION)$(ABIFLAGS)</span><br><span class="line">checking for --with-platlibdir... no</span><br><span class="line">checking whether right shift extends the sign bit... yes</span><br><span class="line">checking for getc_unlocked() and friends... yes</span><br><span class="line">checking how to link readline libs... -lreadline</span><br><span class="line">checking for rl_pre_input_hook in -lreadline... yes</span><br><span class="line">checking for rl_completion_display_matches_hook in -lreadline... yes</span><br><span class="line">checking for rl_resize_terminal in -lreadline... yes</span><br><span class="line">checking for rl_completion_matches in -lreadline... yes</span><br><span class="line">checking for append_history in -lreadline... yes</span><br><span class="line">checking for broken nice()... no</span><br><span class="line">checking for broken poll()... no</span><br><span class="line">checking for working tzset()... yes</span><br><span class="line">checking for tv_nsec in struct stat... no</span><br><span class="line">checking for tv_nsec2 in struct stat... yes</span><br><span class="line">checking curses.h usability... yes</span><br><span class="line">checking curses.h presence... yes</span><br><span class="line">checking for curses.h... yes</span><br><span class="line">checking ncurses.h usability... yes</span><br><span class="line">checking ncurses.h presence... yes</span><br><span class="line">checking for ncurses.h... yes</span><br><span class="line">checking for term.h... yes</span><br><span class="line">checking whether mvwdelch is an expression... yes</span><br><span class="line">checking whether WINDOW has _flags... no</span><br><span class="line">checking for is_pad... yes</span><br><span class="line">checking for is_term_resized... yes</span><br><span class="line">checking for resize_term... yes</span><br><span class="line">checking for resizeterm... yes</span><br><span class="line">checking for immedok... yes</span><br><span class="line">checking for syncok... yes</span><br><span class="line">checking for wchgat... yes</span><br><span class="line">checking for filter... yes</span><br><span class="line">checking for has_key... yes</span><br><span class="line">checking for typeahead... yes</span><br><span class="line">checking for use_env... yes</span><br><span class="line">configure: checking for device files</span><br><span class="line">checking for /dev/ptmx... yes</span><br><span class="line">checking for /dev/ptc... no</span><br><span class="line">checking for %zd printf() format support... yes</span><br><span class="line">checking for socklen_t... yes</span><br><span class="line">checking for broken mbstowcs... no</span><br><span class="line">checking for --with-computed-gotos... no value specified</span><br><span class="line">checking whether clang supports computed gotos... yes</span><br><span class="line">checking for build directories... done</span><br><span class="line">checking for -O2... yes</span><br><span class="line">checking for glibc _FORTIFY_SOURCE/memmove bug... no</span><br><span class="line">checking for stdatomic.h... yes</span><br><span class="line">checking for GCC &gt;= 4.7 __atomic builtins... yes</span><br><span class="line">checking for ensurepip... upgrade</span><br><span class="line">checking if the dirent structure of a d_type field... yes</span><br><span class="line">checking for the Linux getrandom() syscall... no</span><br><span class="line">checking for the getrandom() function... no</span><br><span class="line">checking for library containing shm_open... none required</span><br><span class="line">checking for sys/mman.h... (cached) yes</span><br><span class="line">checking for shm_open... yes</span><br><span class="line">checking for shm_unlink... yes</span><br><span class="line">checking for openssl/ssl.h in /opt/homebrew/opt/openssl@3... yes</span><br><span class="line">checking whether compiling and linking against OpenSSL works... yes</span><br><span class="line">checking for X509_VERIFY_PARAM_set1_host in libssl... yes</span><br><span class="line">checking for --with-ssl-default-suites... python</span><br><span class="line">checking for --with-builtin-hashlib-hashes... md5,sha1,sha256,sha512,sha3,blake2</span><br><span class="line">configure: creating ./config.status</span><br><span class="line">config.status: creating Makefile.pre</span><br><span class="line">config.status: creating Misc/python.pc</span><br><span class="line">config.status: creating Misc/python-embed.pc</span><br><span class="line">config.status: creating Misc/python-config.sh</span><br><span class="line">config.status: creating Modules/ld_so_aix</span><br><span class="line">config.status: creating pyconfig.h</span><br><span class="line">config.status: pyconfig.h is unchanged</span><br><span class="line">creating Modules/Setup.local</span><br><span class="line">creating Makefile</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">If you want a release build with all stable optimizations active (PGO, etc),</span><br><span class="line">please run ./configure --enable-optimizations</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">make: *** No rule to make target `pyconfig.h&#x27;, needed by `Programs/python.o&#x27;.  Stop.</span><br><span class="line">make: *** Waiting for unfinished jobs....</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;No rule to make target &lt;code&gt;pyconfig.h&amp;#39;, needed by &lt;/code&gt;Programs&amp;#x2F;python.o’&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我日常使用 pyenv 管理不同</summary>
      
    
    
    
    <category term="Python" scheme="https://www.yanzhenjie.com/categories/Python/"/>
    
    
    <category term="踩坑" scheme="https://www.yanzhenjie.com/tags/%E8%B8%A9%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>我们为什么不能原谅日本</title>
    <link href="https://www.yanzhenjie.com/post/20250918/3b27a19bde9a/"/>
    <id>https://www.yanzhenjie.com/post/20250918/3b27a19bde9a/</id>
    <published>2025-09-18T01:18:00.000Z</published>
    <updated>2025-09-21T12:05:52.231Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>今时今日，非你我莫属！勿使中华民族断绝，务要使夷狄不敢小视中国，乱臣贼子不敢窥测神器！</p></blockquote><h2 id="一、为什么我们就是不肯原谅日本呢？"><a href="#一、为什么我们就是不肯原谅日本呢？" class="headerlink" title="一、为什么我们就是不肯原谅日本呢？"></a>一、为什么我们就是不肯原谅日本呢？</h2><p>随着南京照相馆和731的相继上映，网上一小部分人就跳了出来，说事情都过去80年了，为什么我们就是不肯原谅日本呢？</p><p>答案就两句话：我们没有资格替先辈去原谅，我们也必须时刻让后辈保持警醒！</p><p>今天是9月18日，94年前的今天，是日本帝国主义侵华的开端，也是无数先辈们14年浴血抗战的起点，<font color='red'><b>这一天对中国人来说，意义太沉重了，我们绝不允许历史重演，你我千万不要觉得现在国家强大了，日子太平了，就不当回事了</b></font>。</p><p>拉开历史的尺度来看，唐朝、元朝、明朝、清朝和民国时期，日本都和我国发生过事关生死的国战，他们一直都妄图把中华大地据为己有。鲁西细菌战，南京大屠杀万人坑遗址等等，也表明了日本人是冲着亡国灭种来的，日本狭隘的国土、贫瘠的资源、频繁的地震与海啸，就注定了日本人永远不会安分。</p><p>就像三体中的三体人，世世代代都在想着星际移民一样，目的都是要鸠占鹊巢，我们不能以常规的视角去看待日本，多灾多难且狭隘的地理环境，已经塑造出了一种另类的民族性格：一面是隐忍，一面是暴力。你若强大，他便折服，你若虚弱，他便趁虚而入。</p><h2 id="二、中日国战史"><a href="#二、中日国战史" class="headerlink" title="二、中日国战史"></a>二、中日国战史</h2><p>我们先来回顾一下自唐朝以来的中日国战史。</p><h3 id="（一）、唐朝时期"><a href="#（一）、唐朝时期" class="headerlink" title="（一）、唐朝时期"></a>（一）、唐朝时期</h3><p><strong>白江口之战（公元663年）</strong></p><p>唐朝与新罗联合对抗日本支援百济，唐军以1.3万人170艘战舰与日军4万人1000艘战舰在白江口决战，唐军以火攻和战术优势大败日军，最终日军战败，百济灭亡，日本被迫退回本土。</p><p>日本此后近千年未敢大规模侵略中国，转而学习唐朝文化，如遣唐使制度，唐朝也巩固了在朝鲜半岛的影响力。</p><h3 id="（二）、元朝时期"><a href="#（二）、元朝时期" class="headerlink" title="（二）、元朝时期"></a>（二）、元朝时期</h3><p><strong>元日战争（1274年、1281年）</strong></p><p>元朝时期，日本当时处于镰仓幕府统治下，幕府将军北条时宗拒绝接受“藩属”地位，镰仓幕府两次拒绝元朝的“诏书”和使者，甚至杀害元朝使节。</p><p>元朝忽必烈于1274年发起第一次远征，史称文永之役，元军3.2万人进攻九州，因后勤不足和风暴而受挫。1281年第二次远征，史称弘安之役，元军14万人（含高丽士兵）渡海，遭遇台风“神风”摧毁舰队。最终元军两次都失败，损失惨重，而使日本民族认同感增强，元朝放弃征服计划，中日关系暂时缓和。</p><h3 id="（三）、明朝时期"><a href="#（三）、明朝时期" class="headerlink" title="（三）、明朝时期"></a>（三）、明朝时期</h3><p><strong>壬辰倭乱（1592-1598年）</strong></p><p>壬辰倭乱也就是明朝时期万历朝鲜战争。明朝末年，明神宗朱翊钧连年不上朝，国家一片混乱，民变频发，财政几近崩溃，日本人发现了可乘之机，于公元1592年，日本前后出动军队总共约30万人进攻朝鲜，万历朝鲜战争爆发。</p><p>而日军攻打朝鲜也只是幌子，当时日军统帅丰臣秀吉猖狂表态攻打朝鲜的目的<font color='red'><b>“就是为了借道朝鲜进入辽东，再直取明都北京，最终目的就是最终征服中国全境”</b></font>。</p><p><font color='red'><b>丰臣秀吉曾扬言“图朝鲜窥视中华，此乃臣之素志”</b></font>。</p><p>当时明朝应藩属国朝鲜请求出兵援朝。明朝与朝鲜联军多次击败日军，但因日军据守坚固城堡（如蔚山城）和制海权劣势，战局胶着。其中有2次关键战役，1593年平壤大捷和碧蹄馆之战。日军于1598年因丰臣秀吉病逝而撤退，明朝未彻底驱逐日军，战争也几乎耗干了明朝的国力，客观上加速了明朝的灭亡。</p><p><strong>争贡之役（1523年）</strong></p><p>日本大内氏与细川氏争夺明朝朝贡贸易权，在宁波爆发冲突。期间两派使者在明朝境内械斗，演变为中日局部冲突。结果使明朝废除宁波市舶司，停止朝贡贸易，最终中日官方贸易中断，倭寇活动加剧，为后期抗倭战争埋下伏笔。</p><h3 id="（四）、清朝时期"><a href="#（四）、清朝时期" class="headerlink" title="（四）、清朝时期"></a>（四）、清朝时期</h3><p><strong>甲午战争（1894-1895年）</strong></p><p>我们都在中学课本上学过中日甲午战争，日本明治维新后，迅速实现工业化，国力增强，但资源匮乏和市场狭小的矛盾日益突出。日本将中国视为扩张的主要目标，朝鲜半岛成为其战略跳板。1894年，朝鲜爆发东学党起义，日本以“保护侨民”为名出兵朝鲜，趁机排挤清朝势力。朝鲜本为清朝附庸国，但日本通过武力控制朝鲜政权，迫使朝鲜断绝与清朝的关系。清朝出兵镇压起义，与日军在朝鲜境内爆发冲突。</p><ul><li>丰岛海战（1894年7月25日）：<font color="red"><b>日本海军偷袭清朝运兵船</b></font>，正式打响战争第一枪。</li><li>黄海海战（1894年9月17日）：北洋舰队与日本联合舰队激战，北洋水师虽损失惨重，但未全军覆没。</li><li>平壤战役（1894年9月）：清军在朝鲜平壤溃败，日军占领朝鲜全境。</li><li>辽东战役（1894年11月-1895年1月）：日军攻占辽东半岛，<font color="red"><b>旅顺大屠杀</b></font> 震惊世界。</li><li>威海卫战役（1895年1月-2月）：日军围攻北洋水师基地威海卫，<font color="red"><b>北洋舰队全军覆没</b></font>。</li></ul><p>结果是清朝战败，1895年4月17日，清政府与日本签订《马关条约》，主要内容包括：</p><ul><li>割让领土：割让辽东半岛、台湾及澎湖列岛，后因三国干涉，清朝以3000万两白银赎回辽东半岛。</li><li>巨额赔款：2亿两白银（相当于清朝3年财政收入）。</li><li>开放通商口岸：沙市、重庆、苏州、杭州等为商埠。</li><li>允许日本在华设厂：列强援引“利益均沾”条款，掀起在华设厂潮。</li></ul><blockquote><p><font color="red"><b>就是这2亿两白银、辛丑条约以及赔付日本的数千万两白银，让日本有了工业革命的资本，为二战日本侵华埋下了祸根</b></font>。</p></blockquote><p>日本把这些赔款，大部分都买了舰艇飞机坦克，少部分投向了重工业，化学工业造船和冶金等领域。用历史专家井上清的话说：“没有甲午战争的赔款，日本工业化至少要推迟20年”。正是因为通过甲午战争<br>和八国联军侵华战争，让日本人凭空获得了天量的财富，于是日本举国上下得到了一个共识，干什么都不如抢，劫来钱快呀。</p><p><font color="red"><b>而且日本军国主义有着深厚的民众基础，你以为甲午战争只是日本少数战争狂热分子发动的吗？大错特错，甲午战争其实是一场日本全民支持的一场军事掠夺，本来当时日本政府是没有钱打这么一场大仗的，但是抵不住日本民众热情高涨呀，没有钱是吧？日本民众就疯狂的买国债，也就是借钱给国家。还不够是吧？日本民众就砸锅卖铁，直接捐赠甚至卖血筹钱给陆军上。不仅如此，日本民众还争先恐后的踊跃参军，很多学生书都不读了，要去为国尽忠，为了海对岸无数的财富以及广袤的土地，日本全民是一片疯狂呀！</b></font></p><p>这可是去杀人呐、去抢劫呀！在这些日本民众眼中，竟然像出国务工一样正常!!!</p><h3 id="（五）、明国时期"><a href="#（五）、明国时期" class="headerlink" title="（五）、明国时期"></a>（五）、明国时期</h3><p><strong>抗日战争（1931-1945年）</strong></p><p>日本侵华战争，分为局部（1931-1937）和全面（1937-1945）两个阶段。</p><p>民国20年，也就是1931年，中国发生百年难遇的特大洪灾，灾情遍及23个省，光江淮流域灾民就高达5127万，死亡约40万人，日本人再次发现了趁虚而入的时机，自然灾害叠加宁越对峙，围剿红军等事件，而且张学良也不在东北，日本在这个时候发动战争明显利益最大。</p><p>第一阶段为九一八事变（1931年），1931年9月18日，日本驻中国东北地区的关东军突然袭击沈阳，九一八事变爆发。随后4个多月，日军陆续占领东三省全境，建立伪满洲国。</p><p>第二阶段为全面抗战（1937年），日军攻占北平、上海、南京（南京大屠杀），我中国军队在台儿庄、武汉等地抵抗。抗日战争期间，国共合作，共产党领导敌后游击战（如百团大战），国民党主导正面战场。</p><p>在1945年8月15日，日本宣布无条件投降，据不完全统计，抗战时期，从<font color='red'><b>中国军民伤亡人数超过3500万，约占第二次世界大战伤亡总人数的1&#x2F;3</b></font>，这其中就包括<font color='red'><b>南京大屠杀的30万遇难者、包括731活体实验的3000个无辜百姓、包括鲁西细菌战遇难的42.7万人、包括被抓到慰安所的20万中国妇女、包括数百万英勇抗战的中国军人</b></font>。</p><p>从<font color='red'><b>九一八事变到1945年日本投降这14年间，日本对华最高投入兵力约410万人</b></font>，他们杀人如麻，无恶不作。</p><p>这是中华民族的国难呀，14年间日本犯下的滔天罪行，如梦魇般萦绕在每一个中国人脑海中，近一个世纪都挥之不去。</p><p>看完以上5个时期的国战，我相信再麻木的人都不可能无动于衷，我相信大家对日本这个国家，应该会有更加清晰的认知。</p><h2 id="三、中华民族的伟大复兴之经略日本"><a href="#三、中华民族的伟大复兴之经略日本" class="headerlink" title="三、中华民族的伟大复兴之经略日本"></a>三、中华民族的伟大复兴之经略日本</h2><blockquote><p>“经略”出自《左传·昭公七年》：“天子经略，诸侯正封，古之制也。”杜预注：“经营天下，略有四海，故曰经略。”</p></blockquote><p>中华名族的伟大复兴，收复台湾自不必多说，如果连祖国统一都做不到，那么别说民族复兴了，估计祖宗的棺材板都压不住了！</p><p>回到主题，依现在的局势看，至少在我们这代人，日本是没有能力再对中国发起全面进攻。但这还远远不够，日本就是一条毒蛇，一旦出现机会，他一定会猛地跳出来狠咬咱们一口。</p><p>日本军备扩张已成客观事实，根据日本政府官方数据及国际机构报告，日本防卫预算近年持续增长：</p><ul><li>2023财年：6.8万亿日元（约460亿美元），创历史新高；</li><li>2024财年：7.9万亿日元（约530亿美元），同比增长16.2%；</li><li>2025财年：8.7万亿日元（约590亿美元），计划进一步扩大。</li></ul><p>具体动向包括：</p><ul><li>军事转型：2023年完成“加贺”号护卫舰改装为“准航母”第一阶段工程，2024年正式启用；</li><li>战略协同：成立“统合作战司令部”，与美军制定联合行动预案；</li><li>技术突破：加速研发高超音速导弹，目标明确指向区域安全格局调整。</li></ul><blockquote><p>注：日本防卫省多次强调其军备发展为“防御性”、“应对地区威胁”，但其行动已显著偏离和平发展轨道。</p></blockquote><p>不是我敏感，可以说日本军国主义的火种从未熄灭，不管我们现在多么强大，都要小心提防着这个挪不走的邻居。正如《孙子兵法》所言：“兵者，国之大事，死生之地，存亡之道，不可不察也。”</p><p>蝉都可以在地下蛰伏17年呢，何况人呢？何况是日本呢？</p><p>因此，我们不能重犯祖宗们犯过的历史错误，在中华强盛的时候，一定要找机会彻底打断日本的脊梁骨，上可告慰列祖列宗，下可荫泽后世子孙。<font color=red><b>如果一定要有人承担历史骂名，那就由你我来承担吧</b></font>！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;今时今日，非你我莫属！勿使中华民族断绝，务要使夷狄不敢小视中国，乱臣贼子不敢窥测神器！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;一、为什么我们就是不肯原谅日本呢？&quot;&gt;&lt;a href=&quot;#一、为什么我们就是不肯原谅日本呢？&quot; class=</summary>
      
    
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/categories/%E6%84%9F%E6%83%B3/"/>
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/tags/%E6%84%9F%E6%83%B3/"/>
    
  </entry>
  
  <entry>
    <title>我们为什么需要”侯亮平“</title>
    <link href="https://www.yanzhenjie.com/post/20250302/7d6fcb150f46/"/>
    <id>https://www.yanzhenjie.com/post/20250302/7d6fcb150f46/</id>
    <published>2025-03-02T09:54:35.000Z</published>
    <updated>2025-03-06T10:11:13.496Z</updated>
    
    <content type="html"><![CDATA[<p>很多人有疑惑，侯亮平一个处长，怎么会受到作为省委书记的沙瑞金去专门迎接？可能沙瑞金也不是很明白上面的用意，可既然让他这么做，那一定是有各种深意在其中。</p><p>刘新建被逮捕之前，他说过这样一句话：”你知道吗？这个新中国是谁打下来的？是我爷爷、我姥姥他们那一辈无产阶级革命家抛头颅洒热血换来的，不是你，你是坐享其成者。”这句话撕开了血淋淋的真相：侯亮平赶上了好时代，盛世里再犯错也能平步青云；侯亮平有个好妻子钟小艾，让他的冒失莽撞总能一次又一次的化险为夷。</p><p>不可否认，侯亮平是这个时代的既得利益者。当人们逐渐理解了祁同伟后，开始厌恶侯亮平高高在上的姿态，痛恨他道貌岸然的正义。可这个国家偏偏需要这样的人，你很难见到”幸运、努力与赤子之心”同时出现在一个人身上，正是这样的人，才能永远秉持正义可守信念。正是钟小艾的通天背景，才让这株温室玫瑰敢于在狂风暴雨中绽放。他的鲁莽需要家族荫蔽托底，他的热血需要红色血脉保温。</p><p>穷人当不了清官，只有一生顺遂无忧，身后永远站着能兜底的靠山，才敢在高速公路上拦李达康的专车，才敢揪着老学长和老师不放手。”富贵不能淫，贫贱不能移，威武不能屈”——这种教科书里的品格，在侯亮平身上成了现实，也只有这样的人，才没有破绽，即便长嘶穿怀也不过是鼓气骨血。因为无需向现实低头，所以浑身没有破绽；因为不曾被生活磋磨，所以连伤口都透着理想主义的血色。</p><p>可惜这样的人终究是凤毛麟角，他们活在真空般的理想国度里，连呼吸都带着不真实的纯粹。有一句诛心的话：侯亮平永远体会不到菜市场里为三毛钱争执的窘迫，理解不了农民工蜷缩在桥洞下的寒凉。不是他冷漠，而是他的金丝笼里从未飞进过真正的风雨。</p><p>所以大家应该明白了：为什么一个副厅级干部需要省委书记亲自迎接？因为寒冬需要这样的火种，盛世需要这样的镜子。我们这些在名利场摆渡的人，早把腰弯成了问号。而侯亮平们始终站成惊叹号，用笔直的脊梁丈量着理想与堕落的距离——这或许就是时代留给清白的最后一块”特权”。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;很多人有疑惑，侯亮平一个处长，怎么会受到作为省委书记的沙瑞金去专门迎接？可能沙瑞金也不是很明白上面的用意，可既然让他这么做，那一定是有各种深意在其中。&lt;/p&gt;
&lt;p&gt;刘新建被逮捕之前，他说过这样一句话：”你知道吗？这个新中国是谁打下来的？是我爷爷、我姥姥他们那一辈无产阶级革</summary>
      
    
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/categories/%E6%84%9F%E6%83%B3/"/>
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/tags/%E6%84%9F%E6%83%B3/"/>
    
  </entry>
  
  <entry>
    <title>让你痛苦的是物质还是执念</title>
    <link href="https://www.yanzhenjie.com/post/20250204/91299a2e3d82/"/>
    <id>https://www.yanzhenjie.com/post/20250204/91299a2e3d82/</id>
    <published>2025-02-04T06:26:47.000Z</published>
    <updated>2025-03-06T10:14:27.400Z</updated>
    
    <content type="html"><![CDATA[<p>在快节奏的现代生活中，我门常常会感到烦恼，然而不管从宏观还是微观来看，你我的工作与生活品质可能比大多数人都要好。</p><p>我们对某些事物的过度追求和执着，可能表现为对财富、地位、名誉的渴望，也可能体现为对完美、控制、认同的执着。这些执念不仅消耗了我们的时间和精力，更让我们在追求的过程中迷失了自我，陷入了无尽的焦虑和痛苦之中。当我们执着于某一目标时，往往会忽视过程中的美好，甚至会因为目标的未能达成而陷入深深的自责和绝望。</p><p>相比之下，物质虽然在一定程度上能够满足我们的生理需求和部分心理需求，但它并非痛苦的根源。事实上，很多时候，我们对物质的过度追求和依赖，恰恰是因为内心的某些执念在作祟。当我们放下这些执念，学会以更加平和的心态去面对生活时，我们会发现，即使物质条件并不优越，我们依然能够感受到幸福和满足。</p><p>也许，你我该来一场说走就走的旅行，放下了对职场成功和物质财富的执念，重新审视了自己的生活和价值观。在旅途中体会生活的真谛，学会用极少的资源去创造快乐，也学会接纳和欣赏生活中的不完美。重新找回内心的平静和力量，也在回归职场后能够以更加轻松和自信的态度去面对工作中的挑战。</p><p>因此，学会放下那些不必要的执念，以更加开放和包容的心态去面对生活中的一切。当我们不再执着于某一目标或某一结果时，我们会更加专注于过程本身，更加珍惜和享受生活中的每一个瞬间。这样的我们，不仅能够更加轻松地走向成功，也能够在人生的旅途中收获更多的幸福和满足。</p><p>总之，执念往往是痛苦的根源，而物质并非决定性的因素。当我们学会放下执念，以更加平和和开放的心态去面对生活时，我们会发现，成功与幸福其实就在我们的身边。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在快节奏的现代生活中，我门常常会感到烦恼，然而不管从宏观还是微观来看，你我的工作与生活品质可能比大多数人都要好。&lt;/p&gt;
&lt;p&gt;我们对某些事物的过度追求和执着，可能表现为对财富、地位、名誉的渴望，也可能体现为对完美、控制、认同的执着。这些执念不仅消耗了我们的时间和精力，更让</summary>
      
    
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/categories/%E6%84%9F%E6%83%B3/"/>
    
    
    <category term="感想" scheme="https://www.yanzhenjie.com/tags/%E6%84%9F%E6%83%B3/"/>
    
  </entry>
  
  <entry>
    <title>Android性能优化之绑定RenderThread到大核CPU</title>
    <link href="https://www.yanzhenjie.com/post/20241221/28b030280983/"/>
    <id>https://www.yanzhenjie.com/post/20241221/28b030280983/</id>
    <published>2024-12-21T13:22:23.000Z</published>
    <updated>2024-12-22T12:13:16.714Z</updated>
    
    <content type="html"><![CDATA[<p>其实本文题目更合适叫做《Android 绑定任意线程到任意 CPU》，其范围也是涵盖了题目的，但是不能体现其最大价值<font color="red"><b>性能优化</b></font>，因此还是缩小了涵盖范围，但是读完本文你还是可以做到 Android 绑定任意线程到任意 CPU 的。</p><span id="more"></span><br/><p>绑定线程到 CPU 的核心就是调用<code>sched_setaffinity</code>函数，但是我们要为此做一些准备：</p><ol><li>了解<code>sched_setaffinity</code>函数，确定绑定某线程到某 CPU 上需要什么参数。</li><li>了解手机多核 CPU 架构，如何获取到每个 CPU 的频率，频率越高的表示性能越高，相反则越差。</li><li>了解 Android 系统中某个进程下的所有线程的各种信息，包括线程 main、RenderThread 等系统线程。</li><li>确定目标线程运行在哪个 CPU 上，用于确定结果。</li></ol><h2 id="1、sched-setaffinity-函数"><a href="#1、sched-setaffinity-函数" class="headerlink" title="1、sched_setaffinity 函数"></a>1、sched_setaffinity 函数</h2><p>该函数用于设置指定进程的 CPU 亲和性，设置线程和 CPU 亲和性应该使用<code>pthread_setaffinity_np</code>，然而在实践中我发现 Android 中没有<code>pthread_setaffinity_np</code>，Android 开发中可以使用<code>sched_setaffinity</code>代替，为了避免偏移主题这里只详细介绍<code>sched_setaffinity</code>。</p><p>函数原型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">sched_setaffinity</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">size_t</span> cpusetsize, <span class="type">const</span> <span class="type">cpu_set_t</span> *mask)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>参数：<ul><li>pid：要设置亲和性的进程的 PID。<font color="red">我们可以用线程 tid （内核级线程 ID）来设置线程和 CPU 的亲和性。</font></li><li>cpusetsize：cpu_set_t 类型的大小。</li><li>mask：指向一个 cpu_set_t 的指针，表示进程可以运行在哪些 CPU 上。</li></ul></li></ul><h2 id="2、CPU-频率"><a href="#2、CPU-频率" class="headerlink" title="2、CPU 频率"></a>2、CPU 频率</h2><h3 id="2-1、认识-CPU-频率"><a href="#2-1、认识-CPU-频率" class="headerlink" title="2.1、认识 CPU 频率"></a>2.1、认识 CPU 频率</h3><p>目前市面上几乎所有手机都是多核，我们用 Perfetto 随便抓个 Trace 就可以看到 CPU 被占用的情况，如图一所示：</p><blockquote><p>不了解 Perfetto 如何分析 Trace 的可以看我文章：<a href="https://www.yanzhenjie.com/post/20240406/b3b882a8973b/">https://www.yanzhenjie.com/post/20240406/b3b882a8973b/</a></p></blockquote><center><img   width="90%"   src="1.png"  ><br/>（图一）</center><p>可以看到我这个手机有 8 个 CPU，其中 CPU7 看起来是最繁忙的，在这个手机上，它是唯一的大核 CPU，也是 main 线程所绑定的 CPU。</p><p>对于任何 Android 手机，我们可以在<code>/sys/devices/system/cpu/</code>目录下看到所有 CPU：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">hw_arm64:/ $ cd /sys/devices/system/cpu/</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ ls</span><br><span class="line">cpu0  cpu1  cpu2  cpu3  cpu4  cpu5  cpu6  cpu7  offline  online  cpufreq  cpuidle  ...</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $</span><br></pre></td></tr></table></figure><p>也可以查看每个 CPU 的频率，以一个 8 核 8 线程的 CPU 为例：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu0/cpufreq/cpuinfo_max_freq 1804800</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu1/cpufreq/cpuinfo_max_freq 1804800</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu2/cpufreq/cpuinfo_max_freq 1804800</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu3/cpufreq/cpuinfo_max_freq 1804800</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu4/cpufreq/cpuinfo_max_freq 2419200</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu5/cpufreq/cpuinfo_max_freq 2419200</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu6/cpufreq/cpuinfo_max_freq 2419200</span><br><span class="line">hw_arm64:/sys/devices/system/cpu $ cat cpu7/cpufreq/cpuinfo_max_freq 2841600</span><br></pre></td></tr></table></figure><p>可以看出来，根据 CPU 频率的不同，可以按照将 CPU 频率分为 3 组，2841600 是唯一一个大核 CPU，也可以看出上文提到的结论。</p><p>至此，我们可以得出 1 个简单的结论：我们可以在代码中获取到手机的 CPU 列表，并可以按照 CPU 频率对 CPU 进行分组。</p><h3 id="2-2、按频率分组-CPU-列表"><a href="#2-2、按频率分组-CPU-列表" class="headerlink" title="2.2、按频率分组 CPU 列表"></a>2.2、按频率分组 CPU 列表</h3><p>根据以上规则，我们可以按照 CPU 频率给 CPU 列表分组：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">./</span><br><span class="line">├── 2841600</span><br><span class="line">│   └── cpu7</span><br><span class="line">├── 2419200</span><br><span class="line">│   ├── cpu6</span><br><span class="line">│   ├── cpu5</span><br><span class="line">│   └── cpu4</span><br><span class="line">└── 1804800</span><br><span class="line">    ├── cpu3</span><br><span class="line">    ├── cpu2</span><br><span class="line">    ├── cpu1</span><br><span class="line">    └── cpu0</span><br></pre></td></tr></table></figure><p>在 C++ 中按照规则读取到这个<code>按频率分组的 CPU 列表</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fstream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;map&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;dirent.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;regex&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;CPUUtils.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;../log/Log.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TAG <span class="string">&quot;CPUUtils&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">const</span> std::regex <span class="title">CPUUtils::cpuNamePattern</span><span class="params">(<span class="string">&quot;^cpu[0-9]+$&quot;</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 获取CPU频率分组map。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return 返回非null的CPU频率分组map，key为频率，value为同频CPU列表。例如：&#123;&quot;888&quot;:[&quot;cpu0&quot;,&quot;cpu1&quot;], &quot;666&quot;:[&quot;cpu2&quot;,&quot;cpu3&quot;]&#125;。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">std::map&lt;<span class="type">int</span>, std::vector&lt;std::string&gt;, std::greater&lt;&gt;&gt; CPUUtils::<span class="built_in">findCPUFreqMap</span>() &#123;</span><br><span class="line">    <span class="type">const</span> std::string cpuDirPath = <span class="string">&quot;/sys/devices/system/cpu&quot;</span>;</span><br><span class="line">    std::map&lt;<span class="type">int</span>, std::vector&lt;std::string&gt;, std::greater&lt;&gt;&gt; freqCPUMap;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *dirPath = cpuDirPath.<span class="built_in">c_str</span>();</span><br><span class="line">    DIR *dir = <span class="built_in">opendir</span>(cpuDirPath.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">if</span> (!dir) &#123;</span><br><span class="line">        <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;findCpuFreqMap, the system/cpu [%s] can be not found.&quot;</span>, dirPath);</span><br><span class="line">        <span class="keyword">return</span> freqCPUMap;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历CPU目录下的CPU列表</span></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">dirent</span> *entry;</span><br><span class="line">    <span class="keyword">while</span> ((entry = <span class="built_in">readdir</span>(dir)) != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="type">const</span> std::string cpuFileName = entry-&gt;d_name;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 只看 CPU+数字的目录，这才是CPU信息目录</span></span><br><span class="line">        <span class="keyword">if</span> (!std::<span class="built_in">regex_match</span>(cpuFileName, CPUUtils::cpuNamePattern)) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取CPU最大频率文件</span></span><br><span class="line">        std::string maxFreqFilePath = cpuDirPath;</span><br><span class="line">        maxFreqFilePath.<span class="built_in">append</span>(<span class="string">&quot;/&quot;</span>);</span><br><span class="line">        maxFreqFilePath.<span class="built_in">append</span>(cpuFileName);</span><br><span class="line">        maxFreqFilePath.<span class="built_in">append</span>(<span class="string">&quot;/cpufreq/cpuinfo_max_freq&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="function">std::ifstream <span class="title">maxFreqFile</span><span class="params">(maxFreqFilePath)</span></span>;</span><br><span class="line">        <span class="keyword">if</span> (!maxFreqFile) &#123;</span><br><span class="line">            <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;findCpuFreqMap, the cpuinfo_max_freq [%s] can be not found.&quot;</span>, maxFreqFilePath.<span class="built_in">c_str</span>());</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 读取CPU最大频率，并按照频率分组记录</span></span><br><span class="line">        std::string line;</span><br><span class="line">        <span class="keyword">if</span> (std::<span class="built_in">getline</span>(maxFreqFile, line)) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="type">int</span> frequency = std::<span class="built_in">stoi</span>(line);</span><br><span class="line">                freqCPUMap[frequency].<span class="built_in">push_back</span>(cpuFileName);</span><br><span class="line">            &#125; <span class="built_in">catch</span> (<span class="type">const</span> std::exception &amp;e) &#123;</span><br><span class="line">                <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;findCpuFreqMap, error occurred on parse frequency: %s.&quot;</span>, e.<span class="built_in">what</span>());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">closedir</span>(dir);</span><br><span class="line">    <span class="keyword">return</span> freqCPUMap;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着上一个方法，可以获取到指定级别的 CPU 列表：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 获取大中小核CPU列表。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param level CPU等级，0为大核，1为中核，2为小核。</span></span><br><span class="line"><span class="comment">  * @param upward CPU序号是否向上兼容，例如如果2找不到则向上找1或者0。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return 返回非null的CPU列表，例如：[&quot;cpu0&quot;, &quot;cpu1&quot;]。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="function">std::vector&lt;std::string&gt; <span class="title">CPUUtils::findCPUListByLevel</span><span class="params">(<span class="type">const</span> <span class="type">int</span> level, <span class="type">const</span> <span class="type">bool</span> upward)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取CPU频率分组</span></span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> findCPUFreqMap = CPUUtils::<span class="built_in">findCPUFreqMap</span>();</span><br><span class="line">    <span class="keyword">if</span> (findCPUFreqMap.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 把std::map的key（频率）按照升序存到std::vector</span></span><br><span class="line">    std::vector&lt;<span class="type">int</span>&gt; freqList;</span><br><span class="line">    freqList.<span class="built_in">reserve</span>(findCPUFreqMap.<span class="built_in">size</span>());</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span> &amp;pair: findCPUFreqMap) &#123;</span><br><span class="line">        freqList.<span class="built_in">push_back</span>(pair.first);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果指定级别不存在，考虑是否向上兼容</span></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> size = <span class="built_in">static_cast</span>&lt;<span class="type">int</span>&gt;(freqList.<span class="built_in">size</span>());</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> freqIndex = size &gt; level ? level : (upward ? (size - <span class="number">1</span>) : <span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 若指定级别存在，则返回频率对应的CPU列表</span></span><br><span class="line">    <span class="keyword">if</span> (freqIndex &gt; <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> freq = freqList[freqIndex];</span><br><span class="line">        <span class="keyword">auto</span> it = findCPUFreqMap.<span class="built_in">find</span>(freq);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (it != findCPUFreqMap.<span class="built_in">end</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> it-&gt;second;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3、RenderThread"><a href="#3、RenderThread" class="headerlink" title="3、RenderThread"></a>3、RenderThread</h2><h3 id="3-1、认识-RenderThread"><a href="#3-1、认识-RenderThread" class="headerlink" title="3.1、认识 RenderThread"></a>3.1、认识 RenderThread</h3><p>RenderThread 是 Android 系统中一个专门用于处理 View 渲染工作的线程，它的引入主要是为了优化渲染效率和提升用户体验。在 Android 5.0（Lollipop）之前，Android 应用程序的 main 线程同时负责处理 UI 绘制和 Open GL 渲染等任务。这可能导致 main 线程在渲染复杂 UI 时出现阻塞，影响应用程序的流畅性。从 Android 5.0 开始，Android 系统引入了 RenderThread，将 UI 渲染任务从主线程中分离出来，从而提高了渲染效率和流畅度。</p><br/>RenderThread 主要功能：<ul><li>RenderThread 负责处理 View 的渲染工作，包括绘制、合成和显示等操作。</li><li>它使用双缓冲机制进行渲染，能够减少屏幕闪烁和卡顿现象。</li><li>RenderThread 还可以提前对 View 进行预渲染，减少渲染任务的延迟。</li></ul><br/>RenderThread 工作原理：<ul><li>当应用程序需要渲染 UI 时，主线程会创建一个渲染任务并提交给 RenderThread。</li><li>RenderThread 在独立的线程中执行渲染任务，并使用 GPU 进行硬件加速渲染。</li><li>渲染完成后，RenderThread 将渲染结果提交给 SurfaceFlinger 进程进行合成和显示。</li></ul><br/>RenderThread 与 main 线程的区别<ul><li>职责不同：main 线程是 Android 应用程序的主入口点，负责处理用户输入、事件分发和 UI 更新等任务。它是应用程序的核心线程，负责执行应用程序的主要逻辑。RenderThread 则专注于处理 View 的渲染工作，减轻主线程的负担，提高渲染效率和流畅度。</li><li>并行处理：main 线程通常是顺序执行任务的，而 RenderThread 可以并行处理多个 View 的渲染任务。这意味着即使某个 View 的渲染任务比较复杂或耗时较长，也不会阻塞其他 View 的渲染或主线程的执行。</li><li>硬件加速：RenderThread 利用 GPU 进行硬件加速渲染，能够显著提高渲染速度和效率。而 main 线程则不直接参与硬件加速渲染过程。</li><li>交互方式：main 线程与 RenderThread 之间通过特定的接口和机制进行交互。例如，主线程可以通过提交渲染任务给 RenderThread 来触发渲染操作，而 RenderThread 则可以通过回调等方式将渲染结果通知给主线程。</li></ul><h3 id="3-2、RenderThread-默认未绑定-CPU"><a href="#3-2、RenderThread-默认未绑定-CPU" class="headerlink" title="3.2、RenderThread 默认未绑定 CPU"></a>3.2、RenderThread 默认未绑定 CPU</h3><p>回到主题，我们来观察 RenderThread 运行在哪个 CPU 上，找到应用的 RenderThread，并在 RenderThread status 横轴找到相对比较繁忙的阶段，选中 Running 阶段，如图三所示它运行在<code>CPU 0</code>，也就是小核 CPU：</p><center><img   width="90%"   src="2.png"  ><br/>（图二）</center><p>偶尔又运行在中核 CPU 或大核 CPU，如图三所示，运行在大核 CPU：</p><center><img   width="90%"   src="3.png"  ><br/>（图三）</center><p>这就是本文题目的原因，如果 RenderThread 一直运行大核 CPU 组，那么渲染性能会不好好一点？</p><h2 id="4、获取任意线程-tid"><a href="#4、获取任意线程-tid" class="headerlink" title="4、获取任意线程 tid"></a>4、获取任意线程 tid</h2><p>对于本文的场景来说，线程有两类，“我”的线程是可以拿到线程实例的，“他”的线程无法拿到线程实例。</p><p>可以获取到实例的：</p><ol><li>当前线程，可以拿到实例，无论 C++ 还是 Java。</li><li>由“我”创建的线程，可以拿到实例，无论 C++ 还是 Java。</li></ol><p>无法获取到实例的：</p><ol><li>比如 main 线程、RenderThread 线程。</li></ol><h3 id="4-1、获取当前线程-tid"><a href="#4-1、获取当前线程-tid" class="headerlink" title="4.1、获取当前线程 tid"></a>4.1、获取当前线程 tid</h3><p>在 C++ 中，获取当前线程的 tid（包括 C++ 的线程或者 Java 的线程），或者由“我”创建&#x2F;启动的线程是可以直接拿到 tid 的。</p><p>获取当前线程的 tid：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">ThreadUtils::bindThreadToCPU</span><span class="params">(<span class="type">const</span> <span class="type">int</span> cpuNumber)</span> </span>&#123;</span><br><span class="line">    <span class="type">pid_t</span> tid = <span class="built_in">gettid</span>();</span><br><span class="line"></span><br><span class="line">    ...;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>获取“我”创建的线程的 tid，使用<code>pthread_gettid_np</code>函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span>* <span class="title">thread_function</span><span class="params">(<span class="type">void</span>* arg)</span> </span>&#123;</span><br><span class="line">    ...;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">startWork</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">pthread_t</span> thread;</span><br><span class="line">    <span class="type">int</span> result = <span class="built_in">pthread_create</span>(&amp;thread, <span class="literal">nullptr</span>, thread_function, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="keyword">if</span> (result != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">LOGI</span>(TAG, <span class="string">&quot;pthread_create failed: %d&quot;</span>, result);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">pid_t</span> tid = <span class="built_in">pthread_gettid_np</span>(thread);</span><br><span class="line">    <span class="built_in">LOGI</span>(TAG, <span class="string">&quot;Created thread tid: %d&quot;</span>, tid);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">pthread_join</span>(thread, <span class="literal">nullptr</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2、获取其他线程的-tid"><a href="#4-2、获取其他线程的-tid" class="headerlink" title="4.2、获取其他线程的 tid"></a>4.2、获取其他线程的 tid</h3><p>当应用启动后，该应用的所有线程信息都位于<code>/proc/$&#123;process_id&#125;/task</code>下，结构如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">├──</span><br><span class="line">├── thread_id1</span><br><span class="line">│   └── status</span><br><span class="line">├── thread_id2</span><br><span class="line">│   └── status</span><br><span class="line">├── thread_id3</span><br><span class="line">│   └── status</span><br><span class="line">├── thread_id4</span><br><span class="line">│   └── status</span><br><span class="line">└── thread_id5</span><br><span class="line">    └── status</span><br></pre></td></tr></table></figure><p>我们可以先在电脑上做一下验证。</p><p>首先，我们查看目标应用的进程 id：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Harry: ~ adb shell ps | grep com.yanzhenjie.android</span><br><span class="line">u0_a130        3303    300 14463340 160012 0                  0 S com.yanzhenjie.android</span><br></pre></td></tr></table></figure><p>然后进入该目录，查看线程列表：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">hw_arm64:/ cd /proc/3303/task</span><br><span class="line">hw_arm64:/proc/3303/task ls</span><br><span class="line">3303  3313  3314  3315  3316  3317  3318  3319  3320  3321  3322  3323  3324  3325  3333  3374  3375  3378  3465</span><br></pre></td></tr></table></figure><p>打印某个线程的信息（为方便理解去掉了真实信息，仅保留结构和描述）：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">hw_arm64:/proc/3303/task cat ./3303/status</span><br><span class="line"></span><br><span class="line">Name:       &lt;线程名&gt;</span><br><span class="line">State:      &lt;状态&gt; (&lt;状态解释&gt;)</span><br><span class="line">Tgid:       &lt;线程组ID&gt;</span><br><span class="line">Ngid:       &lt;线程组ID&gt;</span><br><span class="line">Pid:        &lt;线程ID&gt;</span><br><span class="line">PPid:       &lt;父线程ID&gt;</span><br><span class="line">TracerPid:  &lt;跟踪进程ID&gt;</span><br><span class="line">Uid:        &lt;用户ID&gt; &lt;有效用户ID&gt; &lt;真实用户ID&gt; &lt;已保存用户ID&gt;</span><br><span class="line">Gid:        &lt;组ID&gt; &lt;有效组ID&gt; &lt;真实组ID&gt; &lt;已保存组ID&gt;</span><br><span class="line">FDSize:     &lt;文件描述符表大小&gt;</span><br><span class="line">Groups:     &lt;所属组ID列表&gt;</span><br><span class="line">NStgid:     &lt;命名空间线程组ID&gt;</span><br><span class="line">NSpid:      &lt;命名空间线程ID&gt;</span><br><span class="line">NSpgid:     &lt;命名空间进程组ID&gt;</span><br><span class="line">NSsid:      &lt;命名空间会话ID&gt;</span><br><span class="line">VmPeak:     &lt;虚拟内存峰值&gt;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>我们主要关注：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Name:   RenderThread</span><br><span class="line">Pid:    3314</span><br></pre></td></tr></table></figure><p>到此我们就可以获取到指定名称线程的 tid 了，这里的 pid 就是内核 tid，和<code>/proc/3303/task</code>目录下的子目录名称一致。</p><p>我们可以以线程名称去对应文件和 Name 做匹配，如果匹配到代表上级目录名称或者 Pid 就是内核 tid：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 根据线程名称获取线程id。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param threadName 目标线程名称。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return 没找到返回-1，找到返回线程id。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">ThreadUtils::findThreadId</span><span class="params">(<span class="type">const</span> std::string &amp;threadName)</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> pid = <span class="built_in">getpid</span>();</span><br><span class="line">    <span class="type">const</span> std::string taskDirPath = <span class="string">&quot;/proc/&quot;</span> + std::<span class="built_in">to_string</span>(pid) + <span class="string">&quot;/task&quot;</span>;</span><br><span class="line">    <span class="built_in">LOGD</span>(TAG, <span class="string">&quot;findThreadId on path: %s&quot;</span>, taskDirPath.<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    DIR *dir = <span class="built_in">opendir</span>(taskDirPath.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">if</span> (dir == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;findThreadId, dir [%s] can be not found.&quot;</span>, taskDirPath.<span class="built_in">c_str</span>());</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历线程目录列表</span></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">dirent</span> *entry;</span><br><span class="line">    <span class="keyword">while</span> ((entry = <span class="built_in">readdir</span>(dir)) != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        std::string threadId = entry-&gt;d_name;</span><br><span class="line">        <span class="keyword">if</span> (threadId == <span class="string">&quot;.&quot;</span> || threadId == <span class="string">&quot;..&quot;</span>) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        std::string statusFilePath = taskDirPath;</span><br><span class="line">        statusFilePath.<span class="built_in">append</span>(<span class="string">&quot;/&quot;</span>);</span><br><span class="line">        statusFilePath.<span class="built_in">append</span>(threadId);</span><br><span class="line">        statusFilePath.<span class="built_in">append</span>(<span class="string">&quot;/status&quot;</span>);</span><br><span class="line">        <span class="function">std::ifstream <span class="title">statusFile</span><span class="params">(statusFilePath)</span></span>;</span><br><span class="line">        <span class="keyword">if</span> (statusFile) &#123;</span><br><span class="line">            std::string line;</span><br><span class="line">            <span class="comment">// 遍历status文件中的每一行，找到Name行，并对比名称是否和指定相同</span></span><br><span class="line">            <span class="keyword">while</span> (std::<span class="built_in">getline</span>(statusFile, line)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (line.<span class="built_in">find</span>(<span class="string">&quot;Name&quot;</span>) != std::string::npos) &#123;</span><br><span class="line">                    <span class="type">size_t</span> found = line.<span class="built_in">find</span>(<span class="string">&#x27;:&#x27;</span>);</span><br><span class="line">                    <span class="keyword">if</span> (found != std::string::npos) &#123;</span><br><span class="line">                        std::string name = Utils::<span class="built_in">trim</span>(line.<span class="built_in">substr</span>(found + <span class="number">1</span>));</span><br><span class="line">                        <span class="keyword">if</span> (threadName == name) &#123;</span><br><span class="line">                            <span class="built_in">closedir</span>(dir);</span><br><span class="line">                            <span class="built_in">LOGI</span>(TAG,</span><br><span class="line">                                 <span class="string">&quot;findThreadId, id of [%s] has been found: %s.&quot;</span>,</span><br><span class="line">                                 threadName.<span class="built_in">c_str</span>(),</span><br><span class="line">                                 threadId.<span class="built_in">c_str</span>()</span><br><span class="line">                            );</span><br><span class="line">                            <span class="comment">// 找到目标线程，返回目标线程tid</span></span><br><span class="line">                            <span class="keyword">return</span> std::<span class="built_in">stoi</span>(threadId);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">closedir</span>(dir);</span><br><span class="line">    <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;findThreadId, id of [%s] can be not found.&quot;</span>, threadName.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Utils.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">Utils::trim</span><span class="params">(<span class="type">const</span> std::string &amp;str)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> start = str.<span class="built_in">find_first_not_of</span>(<span class="string">&quot; \t&quot;</span>);</span><br><span class="line">    <span class="keyword">auto</span> end = str.<span class="built_in">find_last_not_of</span>(<span class="string">&quot; \t&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (start == std::string::npos) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> str.<span class="built_in">substr</span>(start, end - start + <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5、设定线程-CPU-亲和性"><a href="#5、设定线程-CPU-亲和性" class="headerlink" title="5、设定线程 CPU 亲和性"></a>5、设定线程 CPU 亲和性</h2><p>再回顾下<code>sched_setaffinity</code>函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">sched_setaffinity</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">size_t</span> cpusetsize, <span class="type">const</span> <span class="type">cpu_set_t</span> *mask)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>参数：<ul><li>pid：要设置亲和性的进程的 PID。<font color="red">我们可以用线程 tid （内核级线程 ID）来设置线程和 CPU 的亲和性。</font></li><li>cpusetsize：cpu_set_t 类型的大小。</li><li>mask：指向一个 cpu_set_t 的指针，表示进程可以运行在哪些 CPU 上。</li></ul></li></ul><p>所以对<code>sched_setaffinity</code>的封装提供 2 个参数，1 个是线程，另一个是 CPU 序号：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 绑定当前线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param cpuNumber CPU序号。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">ThreadUtils::bindThreadToCPU</span><span class="params">(<span class="type">const</span> <span class="type">int</span> cpuNumber)</span> </span>&#123;</span><br><span class="line">    <span class="type">pid_t</span> tid = <span class="built_in">gettid</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> bind = ThreadUtils::<span class="built_in">bindThreadToCPU</span>(tid, cpuNumber);</span><br><span class="line">    <span class="keyword">return</span> bind;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 绑定指定线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param threadId 线程id。</span></span><br><span class="line"><span class="comment">  * @param cpuNumber CPU序号。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">ThreadUtils::bindThreadToCPU</span><span class="params">(<span class="type">const</span> <span class="type">int</span> threadId, <span class="type">const</span> <span class="type">int</span> cpuNumber)</span> </span>&#123;</span><br><span class="line">    <span class="type">cpu_set_t</span> mask;</span><br><span class="line">    <span class="built_in">CPU_ZERO</span>(&amp;mask);</span><br><span class="line">    <span class="built_in">CPU_SET</span>(cpuNumber, &amp;mask);</span><br><span class="line">    <span class="type">long</span> max_cpus = <span class="built_in">sysconf</span>(_SC_NPROCESSORS_CONF);</span><br><span class="line">    <span class="keyword">if</span> (cpuNumber &lt; <span class="number">0</span> || cpuNumber &gt;= max_cpus) &#123;</span><br><span class="line">        <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;bindThreadToCPU, cpu %d is invalid.&quot;</span>, cpuNumber);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> bind = <span class="built_in">sched_setaffinity</span>(threadId, <span class="built_in">sizeof</span>(mask), &amp;mask);</span><br><span class="line">    <span class="keyword">if</span> (bind == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">LOGI</span>(TAG, <span class="string">&quot;bindThreadToCPU, %d, success.&quot;</span>, threadId);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">LOGE</span>(TAG, <span class="string">&quot;bindThreadToCPU, %d, error: %d, %s&quot;</span>, bind, errno, <span class="built_in">strerror</span>(errno));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> bind;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里，所有关键的难点和代码都给出了，剩下的自己随便封装即可，为了完整性，我还是给出所有的代码。</p><h2 id="6、JNI-向-Java-提供接口"><a href="#6、JNI-向-Java-提供接口" class="headerlink" title="6、JNI 向 Java 提供接口"></a>6、JNI 向 Java 提供接口</h2><p>设定当前线程到指定级别 CPU 亲和性的接口：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 绑定当前线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* @param env</span></span><br><span class="line"><span class="comment">* @param clazz</span></span><br><span class="line"><span class="comment">* @param cpuLevel CPU级别，0为大核，1为中核，2为小核。</span></span><br><span class="line"><span class="comment">* @param upward CPU序号是否向上兼容，例如如果2找不到则向上找1或者0。</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* @return 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL <span class="title">Java_com_yanzhenjie_android_Harry_bindCurrentThreadToCPU</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    JNIEnv *env, jclass clazz, jint cpuLevel, jboolean upward)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取指定级别的CPU列表</span></span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> cpuList = CPUUtils::<span class="built_in">findCPUListByLevel</span>(cpuLevel, upward);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!cpuList.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuSize = <span class="built_in">static_cast</span>&lt;<span class="type">int</span>&gt;(cpuList.<span class="built_in">size</span>());</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuIndex = Utils::<span class="built_in">randomInt</span>(<span class="number">0</span>, cpuSize - <span class="number">1</span>);</span><br><span class="line">        <span class="type">const</span> <span class="keyword">auto</span> &amp;cpu = cpuList[cpuIndex];</span><br><span class="line">        <span class="comment">// 去掉前面CPU，只获取num</span></span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuNum = std::<span class="built_in">stoi</span>(cpu.<span class="built_in">substr</span>(<span class="number">3</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设定CPU亲和性</span></span><br><span class="line">        <span class="type">int</span> bound = ThreadUtils::<span class="built_in">bindThreadToCPU</span>(cpuNum);</span><br><span class="line">        Log::<span class="built_in">i</span>(env, TAG,</span><br><span class="line">               <span class="string">&quot;bindCurrentThreadToCPU, level-%s cpu %s: %s&quot;</span>,</span><br><span class="line">               &#123;std::<span class="built_in">to_string</span>(cpuLevel), std::<span class="built_in">to_string</span>(cpuNum), std::<span class="built_in">to_string</span>(bound)&#125;</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> bound;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>设定指定名称的线程到指定级别 CPU 亲和性的接口，比如用于 RenderThread：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 绑定指定线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @param env</span></span><br><span class="line"><span class="comment"> * @param clazz</span></span><br><span class="line"><span class="comment"> * @param threadName 线程名。</span></span><br><span class="line"><span class="comment"> * @param cpuLevel CPU级别，0为大核，1为中核，2为小核。</span></span><br><span class="line"><span class="comment"> * @param upward CPU序号是否向上兼容，例如如果2找不到则向上找1或者0。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @return 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL <span class="title">Java_com_yanzhenjie_android_Harry_bindThreadToCPU</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    JNIEnv *env, jclass clazz, jstring threadName, jint cpuLevel, jboolean upward)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *chars = env-&gt;<span class="built_in">GetStringUTFChars</span>(threadName, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="function">std::string <span class="title">name</span><span class="params">(chars)</span></span>;</span><br><span class="line">    env-&gt;<span class="built_in">ReleaseStringUTFChars</span>(threadName, chars);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据传入线程名称获取线程tid</span></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> threadId = ThreadUtils::<span class="built_in">findThreadId</span>(name);</span><br><span class="line">    <span class="comment">// 根据传入CPU级别获取CPU列表</span></span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> cpuList = CPUUtils::<span class="built_in">findCPUListByLevel</span>(cpuLevel, upward);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (threadId &gt; <span class="number">-1</span> &amp;&amp; !cpuList.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuSize = <span class="built_in">static_cast</span>&lt;<span class="type">int</span>&gt;(cpuList.<span class="built_in">size</span>());</span><br><span class="line">        <span class="comment">// 从对应级别的CPU列表随机选一个CPU</span></span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuIndex = Utils::<span class="built_in">randomInt</span>(<span class="number">0</span>, cpuSize - <span class="number">1</span>);</span><br><span class="line">        <span class="type">const</span> <span class="keyword">auto</span> &amp;cpu = cpuList[cpuIndex];</span><br><span class="line">        <span class="comment">// 去掉前面CPU，只获取num</span></span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> cpuNum = std::<span class="built_in">stoi</span>(cpu.<span class="built_in">substr</span>(<span class="number">3</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设定CPU亲和性</span></span><br><span class="line">        <span class="type">int</span> bound = ThreadUtils::<span class="built_in">bindThreadToCPU</span>(threadId, cpuNum);</span><br><span class="line">        Log::<span class="built_in">i</span>(env, TAG,</span><br><span class="line">               <span class="string">&quot;bindThreadToCPU, bind [%s] to level-[%s] cpu %s : %s&quot;</span>,</span><br><span class="line">               &#123;name, std::<span class="built_in">to_string</span>(cpuLevel), std::<span class="built_in">to_string</span>(cpuNum), std::<span class="built_in">to_string</span>(bound)&#125;</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> bound;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Utils.cpp</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">Utils::randomInt</span><span class="params">(<span class="type">const</span> <span class="type">int</span> min, <span class="type">const</span> <span class="type">int</span> max)</span> </span>&#123;</span><br><span class="line">    std::random_device rd;</span><br><span class="line">    <span class="function">std::mt19937 <span class="title">gen</span><span class="params">(rd())</span></span>;</span><br><span class="line"></span><br><span class="line">    std::uniform_int_distribution&lt;&gt; <span class="built_in">distr</span>(min, max);</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> random_num = <span class="built_in">distr</span>(gen);</span><br><span class="line">    <span class="keyword">return</span> random_num;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下是 JNI 接口对应的 Java 接口：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 绑定当前线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> cpuLevel CPU级别，0为大核，1为中核，2为小核。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> upward CPU序号是否向上兼容，例如如果2找不到则向上找1或者0。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">bindCurrentThreadToCPU</span><span class="params">(<span class="type">int</span> cpuLevel, <span class="type">boolean</span> upward)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 绑定指定线程到指定级别的CPU上。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> threadName 线程名。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> cpuLevel CPU级别，0为大核，1为中核，2为小核。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> upward CPU序号是否向上兼容，例如如果2找不到则向上找1或者0。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 绑定成功返回0，否则返回-1。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">bindThreadToCPU</span><span class="params">(String threadName, <span class="type">int</span> cpuLevel, <span class="type">boolean</span> upward)</span>;</span><br></pre></td></tr></table></figure><p>比如绑定 RenderThread 到中核 CPU：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">bound</span> <span class="operator">=</span> Harry.bindThreadToCPU(<span class="string">&quot;RenderThread&quot;</span>, <span class="number">1</span>, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><p>比如绑定某个工作线程到中核 CPU：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Runnable</span> <span class="variable">runnable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        Thread.currentThread().setName(<span class="string">&quot;PreloadXml&quot;</span>);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">bound</span> <span class="operator">=</span> Harry.bindCurrentThreadToCPU(<span class="number">1</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">        ...;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line">ThreadUtils.getWorkExecutor().execute(runnable);</span><br></pre></td></tr></table></figure><blockquote><p>调用后记得使用 Perfetto 验证是否生效，另外要多考虑一些边界情况，比如一个 4 核 4 线程的手机，可能只有一个频率组。</p></blockquote><p>在实际应用中，我们的 App 可能有一个线程池会专门负责预渲染页面，比如我们可以依次绑定到中核 CPU 组的每个 CPU，提升页面渲染效率。</p><p>本文完，感谢阅读！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;其实本文题目更合适叫做《Android 绑定任意线程到任意 CPU》，其范围也是涵盖了题目的，但是不能体现其最大价值&lt;font color=&quot;red&quot;&gt;&lt;b&gt;性能优化&lt;/b&gt;&lt;/font&gt;，因此还是缩小了涵盖范围，但是读完本文你还是可以做到 Android 绑定任意线程到任意 CPU 的。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://www.yanzhenjie.com/categories/Android/"/>
    
    <category term="C++" scheme="https://www.yanzhenjie.com/categories/Android/C/"/>
    
    
    <category term="线程优化" scheme="https://www.yanzhenjie.com/tags/%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%8C%96/"/>
    
    <category term="C++" scheme="https://www.yanzhenjie.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto Trace性能分析</title>
    <link href="https://www.yanzhenjie.com/post/20240406/adfefb3f3ee8/"/>
    <id>https://www.yanzhenjie.com/post/20240406/adfefb3f3ee8/</id>
    <published>2024-04-06T00:10:12.000Z</published>
    <updated>2024-12-21T14:31:38.667Z</updated>
    
    <content type="html"><![CDATA[<p>本人用 Perfetto 快 2 年了，公司一些同事和网友都来咨询过使用姿势，本人只能把自己一个略显粗糙点的笔记发给同事，有点不够细节和不够深度，于是趁着清明假期，我把经验总结成一篇博客，结合 Perfetto 官网资料取长补短尽量做到深入浅出。</p><span id="more"></span><p>Perfetto 支持多个平台，包括 Linux、Android 和 Chrome，并提供了用于记录系统级和应用级活动的服务和库、低开销的针对 Native 和 Java 的内存分析工具、可供 SQL 分析跟踪文件的 C++库和 Python 库（Python 基于 C++库），以及基于 Web 可视化方便分析 Trace 文件的 Perfetto UI。</p><ul><li>快速开始，Android 抓 Trace：<ul><li><a class="link"   href="https://perfetto.dev/docs/quickstart/android-tracing" >https://perfetto.dev/docs/quickstart/android-tracing<i class="fas fa-external-link-alt"></i></a></li></ul></li><li>可视化工具，Perfetto UI：<ul><li>地址：<a class="link"   href="https://ui.perfetto.dev/" >https://ui.perfetto.dev/<i class="fas fa-external-link-alt"></i></a></li><li>教程：<a class="link"   href="https://perfetto.dev/docs/visualization/perfetto-ui" >https://perfetto.dev/docs/visualization/perfetto-ui<i class="fas fa-external-link-alt"></i></a></li></ul></li><li>开源：<ul><li>地址：<a class="link"   href="https://github.com/google/perfetto" >https://github.com/google/perfetto<i class="fas fa-external-link-alt"></i></a></li><li>贡献：<a class="link"   href="https://perfetto.dev/docs/contributing/getting-started" >https://perfetto.dev/docs/contributing/getting-started<i class="fas fa-external-link-alt"></i></a></li></ul></li></ul><p>聪明的同学从上面的信息中可以了解到很多信息了，下面咱们就展开细节聊一聊。</p><h2 id="1、生成-Perfetto-抓-Trace-的配置"><a href="#1、生成-Perfetto-抓-Trace-的配置" class="headerlink" title="1、生成 Perfetto 抓 Trace 的配置"></a>1、生成 Perfetto 抓 Trace 的配置</h2><p>做 App 性能分析，一般关注这几类信息：</p><ol><li>Memory。</li><li>CPU。</li><li>GPU。</li><li>Power。</li><li>系统层消耗。</li></ol><p>我们在抓 Trace 的时候可以指定应用、内存、CPU、GPU、电源和系统事件等，使用 Perfetto 时可以使用 PerfettoUI 来生成配置：<a class="link"   href="https://ui.perfetto.dev/#!/record" >https://ui.perfetto.dev/#!&#x2F;record<i class="fas fa-external-link-alt"></i></a>。</p><p><strong>第一步</strong>，选择目标 Android 设备系统版本。打开 Perfetto UI，选择要抓 Trace 的目标设备，有 2 种方式，如图一所示：</p><ul><li>方式一，选择目标系统版本。</li><li>方式二，选择 ADB 连接的设备。</li></ul><center><img   width="90%"   src="1.png"  ><br/>（图一）</center><p><strong>第二步</strong>，调整 Trace 文件记录设置。主要是为了控制 Trace 文件大小或者 Trace 记录时长，如果文件太大则加载缓慢操作也不太顺畅，如图二所示：</p><center><img   width="90%"   src="2.gif"  ><br/>（图二）</center><p><strong>第三步</strong>，配置要分析的探针信息。</p><p>收集 CPU 相关信息，需要点击在 Probes 下的 CPU，然后根据需要打开对应开关并做配置，如图三所示：</p><center><img   width="90%"   src="3.png"  ><br/>（图三）</center><p>收集 GPU 相关信息，需要点击在 Probes 下的 GPU，然后根据需要打开对应开关，如图四所示：</p><center><img   width="90%"   src="4.png"  ><br/>（图四）</center><p>收集电源信息和内存信息同上，不再赘述。</p><p>这里需要<font color="red"><b>强调收集 atrace 的配置</b></font>，选中 Probes 下的 Android apps &amp; avcs，如图五所示：</p><center><img   width="90%"   src="5.gif"  ><br/>（图五）</center><p>按照以下操作步骤和注意项：</p><ol><li>打开 atrace 开关。</li><li>默认是抓取所有 app trace，可以关闭<code>Record events from Android apps and services</code>，在下面添加目标 App 包名。</li><li>在 Categories 下选择你关注的 trace 类型。</li></ol><p><strong>第四步</strong>，选择【Recording command】保存配置，一共有 3 种方式。</p><p>方式一，保存在本地，打开【Recording command】的页面后，点击按钮复制配置内容，如图六所示：</p><center><img   width="90%"   src="6.png"  ><br/>（图六）</center><p>方式二，在 Perfetto UI 持久化保存，如图七所示：</p><center><img   width="90%"   src="7.png"  ><br/>（图七）</center><p>方式三，生成远端链接分享给别人下载，如图八所示：</p><center><img   width="90%"   src="8.png"  ><br/>（图八）</center><p>我们可以多尝试下配置会更加熟悉和了解细节，保存后可以根据文档手动增删改一些配置（见下文），比如我们主要关注 CPU 信息时，做好对应设置生成的配置如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line">buffers: &#123;</span><br><span class="line">    size_kb: 63488</span><br><span class="line">    fill_policy: DISCARD</span><br><span class="line">&#125;</span><br><span class="line">buffers: &#123;</span><br><span class="line">    size_kb: 2048</span><br><span class="line">    fill_policy: DISCARD</span><br><span class="line">&#125;</span><br><span class="line">data_sources: &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        name: &quot;android.packages_list&quot;</span><br><span class="line">        target_buffer: 1</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources: &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        name: &quot;android.gpu.memory&quot;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources: &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        name: &quot;linux.process_stats&quot;</span><br><span class="line">        target_buffer: 1</span><br><span class="line">        process_stats_config &#123;</span><br><span class="line">            scan_all_processes_on_start: true</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources: &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        name: &quot;linux.sys_stats&quot;</span><br><span class="line">        sys_stats_config &#123;</span><br><span class="line">            stat_period_ms: 1000</span><br><span class="line">            stat_counters: STAT_CPU_TIMES</span><br><span class="line">            stat_counters: STAT_FORK_COUNT</span><br><span class="line">            #cpufreq_period_ms: 1000</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources: &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        name: &quot;linux.ftrace&quot;</span><br><span class="line">        ftrace_config &#123;</span><br><span class="line">            ftrace_events: &quot;sched/sched_switch&quot;</span><br><span class="line">            ftrace_events: &quot;power/suspend_resume&quot;</span><br><span class="line">            ftrace_events: &quot;sched/sched_wakeup&quot;</span><br><span class="line">            ftrace_events: &quot;sched/sched_wakeup_new&quot;</span><br><span class="line">            ftrace_events: &quot;sched/sched_waking&quot;</span><br><span class="line">            ftrace_events: &quot;power/cpu_frequency&quot;</span><br><span class="line">            ftrace_events: &quot;power/cpu_idle&quot;</span><br><span class="line">            ftrace_events: &quot;power/gpu_frequency&quot;</span><br><span class="line">            ftrace_events: &quot;gpu_mem/gpu_mem_total&quot;</span><br><span class="line">            ftrace_events: &quot;power/gpu_work_period&quot;</span><br><span class="line">            ftrace_events: &quot;raw_syscalls/sys_enter&quot;</span><br><span class="line">            ftrace_events: &quot;raw_syscalls/sys_exit&quot;</span><br><span class="line">            ftrace_events: &quot;sched/sched_process_exit&quot;</span><br><span class="line">            ftrace_events: &quot;sched/sched_process_free&quot;</span><br><span class="line">            ftrace_events: &quot;task/task_newtask&quot;</span><br><span class="line">            ftrace_events: &quot;task/task_rename&quot;</span><br><span class="line">            ftrace_events: &quot;ftrace/print&quot;</span><br><span class="line">            atrace_categories: &quot;am&quot;</span><br><span class="line">            atrace_categories: &quot;aidl&quot;</span><br><span class="line">            atrace_categories: &quot;dalvik&quot;</span><br><span class="line">            atrace_categories: &quot;binder_lock&quot;</span><br><span class="line">            atrace_categories: &quot;camera&quot;</span><br><span class="line">            atrace_categories: &quot;database&quot;</span><br><span class="line">            atrace_categories: &quot;gfx&quot;</span><br><span class="line">            atrace_categories: &quot;network&quot;</span><br><span class="line">            atrace_categories: &quot;sm&quot;</span><br><span class="line">            atrace_categories: &quot;ss&quot;</span><br><span class="line">            atrace_categories: &quot;view&quot;</span><br><span class="line">            atrace_categories: &quot;webview&quot;</span><br><span class="line">            atrace_apps: &quot;com.taobao.android&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">duration_ms: 40000</span><br></pre></td></tr></table></figure><blockquote><p>实际运行时，因为手机系统版本不一致原因，可能遇到以下错误：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-:36:13 error: No field named &quot;cpufreq_period_ms&quot; in proto SysStatsConfig</span><br><span class="line">            cpufreq_period_ms: 1000</span><br></pre></td></tr></table></figure><p>如上面给出的配置示例，注释对应配置行后再尝试。</p></blockquote><p>在实际操作中，会根据不同的场景和需求对抓 Trace 的配置文件做修改，如何修改可以参考官网文档（<a class="link"   href="https://perfetto.dev/docs/" >https://perfetto.dev/docs/<i class="fas fa-external-link-alt"></i></a>）数据源配置，文档位置如图九所示：</p><center><img   width="90%"   src="9.png"  ><br/>（图九）</center><p>生成后好放在电脑的本地（比如桌面），可以把它命名为 perfetto.pbtx。</p><h2 id="2、按照配置抓-Perfetto-Trace"><a href="#2、按照配置抓-Perfetto-Trace" class="headerlink" title="2、按照配置抓 Perfetto Trace"></a>2、按照配置抓 Perfetto Trace</h2><p>让手机抓 Perfetto Trace 有 3 种方式：</p><ol><li>使用手机的 Traceur app 抓取，很多国产手机没有，本文不做介绍。</li><li>使用 adb 命令抓取。</li><li>使用 Perfetto UI 抓取。</li><li>使用 Python 脚本抓取（<font color="red"><strong>推荐</strong></font>）。</li></ol><p>值得注意的是 Perfetto 从 Android 9（P）开始集成，从 Android 11（R）开始默认开启。在 Android 9（P）和 Android 10（Q）上需要先确保开启 Trace 服务：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell setprop persist.traced.enable 1</span><br></pre></td></tr></table></figure><blockquote><p>如果你要在 Android 9（P）之前的设备上使用 Perfetto，请参考官网文档：<br><a class="link"   href="https://perfetto.dev/docs/quickstart/android-tracing#recording-a-trace-through-the-cmdline" >https://perfetto.dev/docs/quickstart/android-tracing#recording-a-trace-through-the-cmdline<i class="fas fa-external-link-alt"></i></a></p></blockquote><p>如果执行了抓取后，没有抓到对应 App 的 Trace，启用<code>systrace VERBOSE</code>再试试：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell setprop log.tag.enableSystrace VERBOSE</span><br></pre></td></tr></table></figure><h3 id="2-1、使用-adb-命令抓取"><a href="#2-1、使用-adb-命令抓取" class="headerlink" title="2.1、使用 adb 命令抓取"></a>2.1、使用 adb 命令抓取</h3><ol><li>通过 adb 把配置推送到手机：<ul><li><code>adb push ~/Desktop/perfetto.pbtx /data/local/tmp/perfetto.pbtx</code></li></ul></li><li>使用 adb 让手机以指定配置抓 Perfetto Trace：<ul><li><code>adb shell &#39;cat /data/local/tmp/perfetto.pbtx | perfetto --txt -c - -o /data/misc/perfetto-traces/trace&#39;</code></li></ul></li><li>结束抓取：<ul><li><code>adb shell &#39;perfetto --attach=perf_debug --stop&#39;</code></li></ul></li></ol><p>抓取完成后，使用 adb 命令导出 Trace 文件到电脑，进行分析即可。该方式操作步骤多略显繁琐，根据实际情况作为备用方式。</p><h3 id="2-2、使用-Perfetto-UI-抓取"><a href="#2-2、使用-Perfetto-UI-抓取" class="headerlink" title="2.2、使用 Perfetto UI 抓取"></a>2.2、使用 Perfetto UI 抓取</h3><p>使用 Perfetto UI 抓取 Trace 时，选择目标 Android 设备系统版本时，需要<font color="red"><strong>选择 ADB 连接的设备</strong></font>，在 Perfetto UI 上配置完成后，不用保存到本地，直接点击【Start Recording】开始录制，如图十所示：</p><center><img   width="90%"   src="10.png"  ><br/>（图十）</center><p>在 Perfetto UI 抓取 Trace 时不能手动结束，需要等在【Recording Setting】中设置的【Max Duration】倒计时结束，当我们点击了录制后会看到如下提示：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Recording in progress for 40000 ms...</span><br></pre></td></tr></table></figure><p>录制结束后，Perfetto UI 会直接在 Web 界面打开 Trace 文件，我们可以直接进行分析。该方式比较方便，配置可以在 Perfetto UI 持久化保存，美中不足的是 Perfetto UI 抓到的 Trace 文件不能下载发给别人。</p><h3 id="2-3、使用-Python-脚本抓取"><a href="#2-3、使用-Python-脚本抓取" class="headerlink" title="2.3、使用 Python 脚本抓取"></a>2.3、使用 Python 脚本抓取</h3><p>使用 Python 脚本抓取到 Trace 后，会把 Trace 文件保存到本地，也会自动在浏览器通过 Perfetto UI 直接打开 Trace 文件，我们直接进行分析。</p><p>使用 Python 脚本抓取时需要满足以下几个条件：</p><ol><li>Android 设备通过 adb 连接到电脑。</li><li>把 Python 脚本保存在本地，在本地运行 Python 脚本。</li><li>把抓 Trace 的配置保存在本地，运行 Python 脚本时需要指定配置文件。</li></ol><p>Python 脚本在 GitHub 上的开源地址，可以通过浏览器看源码：<br><a class="link"   href="https://github.com/google/perfetto/blob/main/tools/record_android_trace" >https://github.com/google/perfetto/blob/main/tools/record_android_trace<i class="fas fa-external-link-alt"></i></a><br>raw.githubusercontent.com 下载链接：<br><a class="link"   href="https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace" >https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace<i class="fas fa-external-link-alt"></i></a></p><p>现在我们把 Python 脚本和抓 Trace 的配置放在桌面，命名和目录结构如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~/Desktop$</span><br><span class="line">├── perfetto.py</span><br><span class="line">├── perfetto.pbtx</span><br></pre></td></tr></table></figure><p>此时我们手机与电脑通过 adb 连接，然后运行以下命令抓取 Trace：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 perfetto.py -c perfetto.pbtx -o trace_file.perfetto-trace</span><br></pre></td></tr></table></figure><blockquote><p>上述命令中，<code>-c</code>是指定配置文件位置，<code>-o</code>是指定 trace 文件保存位置。</p></blockquote><p>运行命令后，我们开始操作 App，然后觉得抓取到目标 Trace 了，按下<code>ctrl + c</code>结束即可，此时 Trace 文件会被放在<code>-o</code>指定的位置，且 Perfetto UI 会被自动打开，我们直接进行分析即可。</p><blockquote><p>而且我们不用担心抓的 Trace 太大网站内存绷不住，因为 Python 脚本会自动下载当前系统的 trace_processor 来解析，细节可以从官网了解。<br><a class="link"   href="https://perfetto.dev/docs/visualization/large-traces" >https://perfetto.dev/docs/visualization/large-traces<i class="fas fa-external-link-alt"></i></a></p></blockquote><h2 id="3、打-Trace"><a href="#3、打-Trace" class="headerlink" title="3、打 Trace"></a>3、打 Trace</h2><p>先大概了解下 Trace 的基本概念，一个 Trace 段在 Perfetto UI 中用 Slice 表示，它并不代表一个方法，而是一个 Trace 的开始到结束，需要在代码中标记。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Trace.beginSection(<span class="string">&quot;Choreographer#doFrame&quot;</span>);</span><br><span class="line">...</span><br><span class="line">Trace.endSection();</span><br></pre></td></tr></table></figure><p>如上述代码所示，这是一个同步 Trace，一个同步 Trace 段必须是一个<code>Trace#beginSection(String)</code>对应一个<code>Trace#endSection()</code>，否则抓取到的 Trace 将会异常。</p><h3 id="3-1、同步-Trace"><a href="#3-1、同步-Trace" class="headerlink" title="3.1、同步 Trace"></a>3.1、同步 Trace</h3><p>Trace 也可以嵌套多层，比如我们知道某个方法很耗时，但是该方法由很多代码段组成，那我们就可以给该方法中每一段代码加上 Trace，来分析具体是哪一段代码引起的耗时，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">    Trace.beginSection(<span class="string">&quot;First&quot;</span>);</span><br><span class="line">    ...;</span><br><span class="line">        Trace.beginSection(<span class="string">&quot;Second&quot;</span>);</span><br><span class="line">        ...;</span><br><span class="line">            Trace.beginSection(<span class="string">&quot;Third&quot;</span>);</span><br><span class="line">            ...;</span><br><span class="line">            Trace.endSection();</span><br><span class="line">        Trace.endSection();</span><br><span class="line">    Trace.endSection();</span><br><span class="line"></span><br><span class="line">    Trace.beginSection(<span class="string">&quot;AAA&quot;</span>);</span><br><span class="line">    ...;</span><br><span class="line">        Trace.beginSection(<span class="string">&quot;BBB&quot;</span>);</span><br><span class="line">        ...;</span><br><span class="line">        Trace.endSection();</span><br><span class="line">    Trace.endSection();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意：同步 Trace 的<code>Trace.beginSection(String)</code>和<code>Trace.endSection()</code>需要在同一个线程调用。</p></blockquote><p>如上代码，为了方便看清结构，我添加了缩进，我们将得到如图十一所示的 Trace：</p><center><img   width="90%"   src="2.1.png"  ><br/>（图十一）</center><h3 id="3-2、异步-Trace"><a href="#3-2、异步-Trace" class="headerlink" title="3.2、异步 Trace"></a>3.2、异步 Trace</h3><p>我认为，异步 Trace 具有以下特点：</p><ol><li>异步 Trace 可以解决同步 Trace 不能跨线程的问题。</li><li>异步 Trace 不用考虑<code>begin</code>和<code>end</code>是否在同一个线程。</li><li>异步 Trace 可以观察流程耗时，比同步 Trace 更宏观。</li></ol><h4 id="3-2-1、同步-Trace-的局限性"><a href="#3-2-1、同步-Trace-的局限性" class="headerlink" title="3.2.1、同步 Trace 的局限性"></a>3.2.1、同步 Trace 的局限性</h4><p>我们先看一个示例，我们想统计<code>request()</code>方法和工作线程的整体耗时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">request</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ThreadPoolExecutor</span> <span class="variable">executors</span> <span class="operator">=</span> ...;</span><br><span class="line">    Trace.beginSection(<span class="string">&quot;request&quot;</span>);</span><br><span class="line"></span><br><span class="line">    ...;</span><br><span class="line"></span><br><span class="line">    executors.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            ...;</span><br><span class="line"></span><br><span class="line">            notifyReuslt();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    Trace.endSection();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上代码所示，其结果只是统计了<code>ThreadPoolExecutor#execute</code>的耗时，工作线程的耗时没有统计到。</p><blockquote><p>上文提到过，同步 Trace 不能跨线程使用，因此<code>Trace.endSection()</code>不能放在<code>run()</code>结尾。</p></blockquote><p>依然是如上需求，我们换一种姿势：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">request</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ThreadPoolExecutor</span> <span class="variable">executors</span> <span class="operator">=</span> ...;</span><br><span class="line">    Trace.beginSection(<span class="string">&quot;request&quot;</span>);</span><br><span class="line"></span><br><span class="line">    ...;</span><br><span class="line"></span><br><span class="line">    executors.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            Trace.beginSection(<span class="string">&quot;request#run&quot;</span>);</span><br><span class="line">            ...;</span><br><span class="line"></span><br><span class="line">            notifyReuslt();</span><br><span class="line">            Trace.endSection();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    Trace.endSection();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码，抓取 Trace 后会，2 个 Trace 会分别分布在主线程和工作线程，虽然可以人工计算得出整体耗时，但是非常不方便、不直观。如果在工作流程中切换了好几个线程，就更加麻烦了。</p><blockquote><p>大胆想象，大型应用中对统计切换多个线程的整体耗时情况非常常见，下文会介绍</p></blockquote><h4 id="3-2-2、异步-Trace-使用"><a href="#3-2-2、异步-Trace-使用" class="headerlink" title="3.2.2、异步 Trace 使用"></a>3.2.2、异步 Trace 使用</h4><p>异步 Trace 的基本使用：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TraceUtils.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">beginAsync</span><span class="params">(String name, <span class="type">int</span> cookie)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q) &#123;</span><br><span class="line">        Trace.beginAsyncSection(name, cookie);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">endAsync</span><span class="params">(String name, <span class="type">int</span> cookie)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q) &#123;</span><br><span class="line">        Trace.endAsyncSection(name, cookie);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>例如，还是以上述代码为例，我们统计<code>request()</code>整体耗时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">request</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ThreadPoolExecutor</span> <span class="variable">executors</span> <span class="operator">=</span> ...;</span><br><span class="line">    TraceUtils.beginAsync(<span class="string">&quot;request&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    ...;</span><br><span class="line"></span><br><span class="line">    executors.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            ...;</span><br><span class="line"></span><br><span class="line">            TraceUtils.beginAsync(<span class="string">&quot;handleData&quot;</span>, <span class="number">1</span>);</span><br><span class="line">            ...;</span><br><span class="line">            TraceUtils.endAsync(<span class="string">&quot;handleData&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">            notifyReuslt();</span><br><span class="line">            TraceUtils.endAsync(<span class="string">&quot;request&quot;</span>, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">notifyResult</span><span class="params">()</span> &#123;</span><br><span class="line">    ThreadUtils.runOnMain(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            TraceUtils.beginAsync(<span class="string">&quot;notifyResult&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">            ...;</span><br><span class="line"></span><br><span class="line">            TraceUtils.endAsync(<span class="string">&quot;notifyResult&quot;</span>, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们将得到如图十二所示 Trace：</p><center><img   width="90%"   src="2.2.png"  ><br/>（图十二）</center><p>再例如，当<code>A1-request</code>整个流程跨越了好几个线程时，使用同步 Trace 是无法将它们聚合到一起的，如果使用异步 Trace，那么我们可以得到如图十三所示 Trace：</p><center><img   width="90%"   src="2.3.png"  ><br/>（图十三）</center><p>在复杂的场景的性能分析和优化是非常有用的，比如 App 冷启动，有很多初始化任务、首页请求任务、首页渲染、图像加载等，非常依赖异步 Trace 做流程分析。</p><h4 id="3-2-3、异步-Trace-封装"><a href="#3-2-3、异步-Trace-封装" class="headerlink" title="3.2.3、异步 Trace 封装"></a>3.2.3、异步 Trace 封装</h4><p>通过上文我们看到，打异步 Trace 时除了要传一个<code>name</code>还要传一个<code>cookie</code>，它的原理就和<code>token</code>一样，因为异步 Trace 跨越多个线程，也可能同一个<code>name</code>发生多次，所以需要<code>cookie</code>来做流程唯一标记，否则第二次发生就会覆盖第一次发生。</p><p>同时，当我们一次流程明确时，上述使用方式就极为不便（需要记录 cookie），而且当流程结束时，流程中一些尚未标记的异步 Trace（可能是一些后续完成的异步任务）发生时，会打乱该流程中的 Trace 记录。</p><p>封装一个自动处理<code>cookie</code>的<code>Async</code>Trace：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TraceUtils.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步Trace，cookie生成器</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">COOKIE_MAKER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Async</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> cookie;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> finished;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Async</span><span class="params">()</span> &#123;</span><br><span class="line">        cookie = COOKIE_MAKER.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(String methodName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!finished) &#123;</span><br><span class="line">            TraceUtils.beginAsync(methodName, cookie);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">end</span><span class="params">(String methodName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!finished) &#123;</span><br><span class="line">            TraceUtils.endAsync(methodName, cookie);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">finish</span><span class="params">()</span> &#123;</span><br><span class="line">        finished = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isFinished</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> finished;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们<code>new Async()</code>生成一个实例时，这个 cookie 将会被记录，使用这个实例记录的所以 Trace 的<code>begin</code>和<code>end</code>对的<code>cookie</code>将自动统一。</p><p>为了方便获取<code>一个流程</code>（比如页面秒开）的实例，还需要封装获取实例的类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TraceUtils.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步Trace列表</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Async&gt; ASYNC_LIST = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> biz 流程名称。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Async</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Async <span class="title function_">async</span><span class="params">(String biz)</span> &#123;</span><br><span class="line">    <span class="type">Async</span> <span class="variable">async</span> <span class="operator">=</span> ASYNC_LIST.get(biz);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (async != <span class="literal">null</span> &amp;&amp; async.isFinished()) &#123;</span><br><span class="line">        ASYNC_LIST.remove(biz);</span><br><span class="line">        async = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (async == <span class="literal">null</span>) &#123;</span><br><span class="line">        async = <span class="keyword">new</span> <span class="title class_">AsyncTrace</span>();</span><br><span class="line">        ASYNC_LIST.put(biz, async);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> async;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Async <span class="title function_">launch</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> TraceUtils.async(<span class="string">&quot;launch&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用上述简单的封装，我们的使用姿势将是这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">HarryTrace.async(biz).begin(<span class="string">&quot;A1-request&quot;</span>);</span><br><span class="line">...;</span><br><span class="line"></span><br><span class="line">    HarryTrace.async(biz).begin(<span class="string">&quot;A2-connect&quot;</span>);</span><br><span class="line">    ...;</span><br><span class="line">    HarryTrace.async(biz).end(<span class="string">&quot;A2-connect&quot;</span>);</span><br><span class="line"></span><br><span class="line">    HarryTrace.async(biz).begin(<span class="string">&quot;A3-sendData&quot;</span>);</span><br><span class="line">    ...;</span><br><span class="line">    HarryTrace.async(biz).end(<span class="string">&quot;A3-sendData&quot;</span>);</span><br><span class="line"></span><br><span class="line">HarryTrace.async(biz).end(<span class="string">&quot;A1-request&quot;</span>);</span><br></pre></td></tr></table></figure><h4 id="3-2-4、复杂异步-Trace-示例"><a href="#3-2-4、复杂异步-Trace-示例" class="headerlink" title="3.2.4、复杂异步 Trace 示例"></a>3.2.4、复杂异步 Trace 示例</h4><p>如 3.2.2 中末尾所示，我们可以在一个复杂的流程中使用异步 Trace 方便我们分析整个流程的耗时情况，对于每一个阶段的方法再使用同步 Trace 分析具体代码耗时。</p><p>给一个实际应用中复杂的例子，这是从 App 启动、预定位、预加载、首页渲染完成到用户可交互的耗时情况，由于截图弄不了那么大，没有加应用启动阶段<code>Application#onCreate</code>的耗时情况，如图十四所示：</p><center><img   width="90%"   src="2.4.png"  ><br/>（图十四）</center><h3 id="3-3、常见问题"><a href="#3-3、常见问题" class="headerlink" title="3.3、常见问题"></a>3.3、常见问题</h3><p>文章发出后，有人问我为什么 Release 包抓不到 Trace？我总结了以下几点（不同版本系统不一样，建议以下选项可以全部尝试配置）：</p><ol><li>执行 adb 命令开启 Trace，上文中有提到。</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb shell setprop persist.traced.enable 1</span><br><span class="line">adb shell setprop log.tag.enableSystrace VERBOSE</span><br></pre></td></tr></table></figure><ol start="2"><li>检查 manifest.xml 中是否配置了 profileable。<blockquote><p>官网文档：<a class="link"   href="https://developer.android.com/guide/topics/manifest/profileable-element" >https://developer.android.com/guide/topics/manifest/profileable-element<i class="fas fa-external-link-alt"></i></a></p></blockquote></li><li>是否在打 Release 包时去掉了 Trace，一般是打包插件做的，不同公司方式不同。（<font color="red"><b>最有可能的</b></font>）</li><li>系统可能禁用了 App Trace，可以反射开启，也可以包装 Trace 类在发布版中不打 Trace。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="variable">initialized</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">startTrace</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">allowTrace</span> <span class="operator">=</span> BuildConfig.TRACE;</span><br><span class="line">    <span class="keyword">if</span> (!allowTrace || initialized) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    initialized = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    Class&lt;?&gt; trace = Trace.class;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//noinspection JavaReflectionMemberAccess</span></span><br><span class="line">        <span class="meta">@SuppressLint(&quot;DiscouragedPrivateApi&quot;)</span></span><br><span class="line">        <span class="type">Method</span> <span class="variable">setAppTracingAllowed</span> <span class="operator">=</span> trace.getDeclaredMethod(<span class="string">&quot;setAppTracingAllowed&quot;</span>, <span class="type">boolean</span>.class);</span><br><span class="line">        setAppTracingAllowed.invoke(<span class="literal">null</span>, <span class="literal">true</span>);</span><br><span class="line">        Logger.i(TAG, <span class="string">&quot;startTrace end&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">        Logger.e(TAG, t);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意<code>BuildConfig.TRACE</code>需要在 App 模块的构建脚本 build.gradle 的<code>buildTypes</code>中配置生成，在线下 Release 包开启 Trace，在发布到市场的包中关闭 Trace，以免被黑产利用。</p><p>相对应的，我们需要调整我们的封装类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(String sectionName)</span> &#123;</span><br><span class="line">    startTrace();</span><br><span class="line">    <span class="keyword">if</span> (initialized) &#123;</span><br><span class="line">        ...;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (initialized) &#123;</span><br><span class="line">        ...;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">beginAsync</span><span class="params">(String methodName, <span class="type">int</span> cookie)</span> &#123;</span><br><span class="line">    startTrace();</span><br><span class="line">    <span class="keyword">if</span> (initialized) &#123;</span><br><span class="line">        ...;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">endAsync</span><span class="params">(String methodName, <span class="type">int</span> cookie)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (initialized) &#123;</span><br><span class="line">        ...;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Async <span class="title function_">async</span><span class="params">(String biz)</span> &#123;</span><br><span class="line">    startTrace();</span><br><span class="line">    ...;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4、Trace-分析"><a href="#4、Trace-分析" class="headerlink" title="4、Trace 分析"></a>4、Trace 分析</h2><h3 id="4-1、使用图形界面分析"><a href="#4-1、使用图形界面分析" class="headerlink" title="4.1、使用图形界面分析"></a>4.1、使用图形界面分析</h3><p>正式操作之前先掌握一些基本操作技巧。</p><p>基本操作 1，打开 Trace 文件后，可以整体上观察哪些线程有问题，比如<code>耗时久</code>、<code>Uninterruptiable Sleep (non-IO)</code>等，定位到有问题的线程或者 Trace 段后，我们就可以对线程和 Trace 段进行更详细的分析了，如图十五所示：</p><center><img   width="90%"   src="11.gif"  ><br/>（图十五）</center><p>基本操作 2，在对线程和 Trace 段进行详细分析之前，对要分析的进程和线程做下钉操作（如图十六所示）：</p><ol><li>在列表中点击要分析的 App 进程，点击后该进程会展开子列表，展示所有线程。</li><li>找到<code>RenderThread</code>（渲染线程）并点击名称右侧的钉图标，把该线程钉在列表顶部。</li></ol><center><img   width="90%"   src="12.png"  ><br/>（图十六）</center><p>这样做的目的是方便观察<strong>主线程</strong>和<strong>渲染线程</strong>发生了什么，找到性能瓶颈所在。</p><p>下面我们一起看 3 个例子。</p><h4 id="4-1-1、Uninterruptible-Sleep-non-IO"><a href="#4-1-1、Uninterruptible-Sleep-non-IO" class="headerlink" title="4.1.1、Uninterruptible Sleep (non-IO)"></a>4.1.1、Uninterruptible Sleep (non-IO)</h4><p>我们看一个最经典的<font color="red"><strong>方法耗时分析</strong></font>，看过 Framework 源码的同学都知道系统层<code>Choreographer#doFrame()</code>是执行一帧绘制（包括<code>layout</code>、<code>measure</code>、<code>draw</code>），如果我们页面操作时（比如浏览信息流）略显卡顿，那么必定存在某一帧或者更多帧耗时久的情况。用 Perfetto UI 打开 Trace，一眼就能看到有些些 Trace 段比较长，选中该段 Trace（这个 Trace 是系统层加的），如图十七所示：</p><center><img   width="90%"   src="13.png"  ><br/>（图十七）</center><p>观察 Slice Details 后会很一目了然的看到该 Trace 的耗时：</p><ol><li>看到这一帧耗时 177ms，很明显这么久的一帧会肉眼可见的卡顿一下。</li><li>查看 Duration 列表，可以看到有意外，存在耗时较久的<code>Sleeping</code>和<code>Uninterruptible Sleep (non-IO)</code>。</li><li>Running 比较久，可以点击箭头展开查看细节。</li></ol><p>很明显，主线程存在不符合预期的<code>Uninterruptible Sleep (non-IO)</code>状态，这个 case 中比较明显的棕色段就是<code>Uninterruptible Sleep</code>的信息（不明显的放大横轴段就能看到）。</p><p>重点来了，<font color="red"><strong>如何定位是什么原因引起的 Uninterruptible Sleep？</strong></font>我们可以这样操作，以<code>Uninterruptible Sleep</code>为中心放大横轴，到足够大了可以看到 App 主线程行慢慢露出<code>Runnable</code>和<code>Running</code>，如图十八所示：</p><center><img   width="90%"   src="14.png"  ><br/>（图十八）</center><ol><li><code>Runnable</code>不表示线程，而是表示当前线程可运行了。</li><li><code>Running</code>表示当前线程正在运行。</li></ol><p>此时我们选中距离<code>Uninterruptible Sleep</code>最近的<code>Runnable</code>，如图十九所示：</p><center><img   width="90%"   src="15.png"  ><br/>（图十九）</center><p>在<code>Selection Details</code>中可以看到<code>Waker</code>信息，它表示唤起当前线程（这里是主线程）的线程，可以看到这里是<code>dp2ndk</code>线程阻塞了主线程。同时，我们在 App 进程子列表中找到<code>dp2ndk</code>线程，并钉住。此时<code>dp2ndk</code>、渲染线程、主线程等就会同时钉在顶部，方便我们做对比确认信息。</p><p>从图十五可以看到，<code>dp2ndk</code>线程占用了 CPU，这里需要做优化。</p><h4 id="4-1-2、monitor-contention-with-owner"><a href="#4-1-2、monitor-contention-with-owner" class="headerlink" title="4.1.2、monitor contention with owner"></a>4.1.2、monitor contention with owner</h4><p>在 Perfetto UI 打开 Trace，放大缩小横轴到适当大小，横向滑动 Trace 段，可以看到类似<code>monitor contention with owner [xxx]</code>的 Trace 段，它会阻塞主线程或者渲染线程，点击主线程或者渲染线程后面的<code>Runnable</code>观察<code>Waker</code>，可以看到是哪个线程阻塞的，如图二十所示：</p><center><img   width="90%"   src="16.png"  ><br/>（图二十）</center><p>此时我们选中距离<code>Uninterruptible Sleep</code>最近的<code>Runnable</code>，从图十六的<code>Slice Details</code>中可以看到，<code>NetworkKit-GRS_</code>线程占用了 CPU，这里需要做优化。</p><h4 id="4-1-3、Lock-contention-on-InternTable-lock"><a href="#4-1-3、Lock-contention-on-InternTable-lock" class="headerlink" title="4.1.3、Lock contention on InternTable lock"></a>4.1.3、Lock contention on InternTable lock</h4><p><code>Lock contention on [xxx]</code>的类型比较多，都属于锁操作，在 App 启动过程中或者页面操作过程中非常常见，如图十七所示，发现某个 Trace 耗时较长时，单独选中该段 Trace，在 Slices 中会列出子 Trace 段列表，可以更好的看出耗时 Trace。</p><p>类似<code>Lock contention on InternTable lock</code>的渲染帧会影响页面流畅性，造成 App 卡顿，如图二十一所示：</p><center><img   width="90%"   src="17.gif"  ><br/>（图二十一）</center><h3 id="4-2、使用-SQL-聚合分析"><a href="#4-2、使用-SQL-聚合分析" class="headerlink" title="4.2、使用 SQL 聚合分析"></a>4.2、使用 SQL 聚合分析</h3><p>官网文档：</p><ul><li>常见查询：<a class="link"   href="https://perfetto.dev/docs/analysis/common-queries" >https://perfetto.dev/docs/analysis/common-queries<i class="fas fa-external-link-alt"></i></a></li><li>语法：<a class="link"   href="https://perfetto.dev/docs/analysis/perfetto-sql-syntax" >https://perfetto.dev/docs/analysis/perfetto-sql-syntax<i class="fas fa-external-link-alt"></i></a></li><li>表字段介绍：<a class="link"   href="https://perfetto.dev/docs/analysis/sql-tables" >https://perfetto.dev/docs/analysis/sql-tables<i class="fas fa-external-link-alt"></i></a>。</li></ul><p>使用图形界面只能一个个 Case 具体分析，当我们梳理出具体的多个 Cas 时，有时候需要拿到聚合数据给对应同事做优化，可以使用 SQL 查询。</p><p>例如，统计名为<code>renderConvertView[smart_ui_jiangliu]</code>的 Trace 的平均耗时：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> name, <span class="built_in">AVG</span>(dur)<span class="operator">/</span><span class="number">1000</span> <span class="keyword">AS</span> dur <span class="keyword">FROM</span> slice <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;renderConvertView[smart_ui_jiangliu]&#x27;</span> <span class="keyword">GROUP</span> <span class="keyword">BY</span> name</span><br></pre></td></tr></table></figure><p>结果如图二十二所示：</p><center><img   width="90%"   src="18.png"  ><br/>（图二十二）</center><p>例如，统计名为<code>Lock contention on thread list lock (owner tid: 4054)</code>的 Trace，并按照耗时降序排序：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> ts, dur, name <span class="keyword">FROM</span> slice <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;read from memory&#x27;</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> dur <span class="keyword">DESC</span></span><br></pre></td></tr></table></figure><p>结果如图二十三所示：</p><center><img   width="90%"   src="19.png"  ><br/>（图二十三）</center><h2 id="5、结语"><a href="#5、结语" class="headerlink" title="5、结语"></a>5、结语</h2><p>本文介绍了 Perfetto 配置生成、抓取 Trace、使用 Perfetto UI 分析 Trace 三块内容。在 Trace 分析时主要介绍了基于 CPU 的分析，没有介绍 GPU、内存、电量分析的细节，因为日常工作中本人用的不是很多，所以暂时写不了就不误导读者了，有机会再补充一篇文章。</p><p>本文介绍的内容是不到官网的 1&#x2F;3 的，但是一些细节也是官网没有的，很多人在实操时就卡在这些细节上，这也是我写这篇文章的原因。</p><p>感兴趣的读者可以加我的 QQ 交流群：<a class="link"   href="https://qm.qq.com/cgi-bin/qm/qr?k=6RdPw9r9yT8nqjEuKJkA-2kHmHwDCVXY&jump_from=webapi&authKey=bBM/fnWLYuyhTSkKofUYaNLQUlMkRuH+fIx7lF6/pkeYylo4MZQxbt0mJKqJo5rq" >46523908<i class="fas fa-external-link-alt"></i></a>。</p><p>本文完，感谢阅读。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本人用 Perfetto 快 2 年了，公司一些同事和网友都来咨询过使用姿势，本人只能把自己一个略显粗糙点的笔记发给同事，有点不够细节和不够深度，于是趁着清明假期，我把经验总结成一篇博客，结合 Perfetto 官网资料取长补短尽量做到深入浅出。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://www.yanzhenjie.com/categories/Android/"/>
    
    <category term="性能优化" scheme="https://www.yanzhenjie.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
    <category term="Prefetto" scheme="https://www.yanzhenjie.com/tags/Prefetto/"/>
    
  </entry>
  
  <entry>
    <title>人工智能|GPU加速PyTorch训练</title>
    <link href="https://www.yanzhenjie.com/post/20240313/52aafdd7288f/"/>
    <id>https://www.yanzhenjie.com/post/20240313/52aafdd7288f/</id>
    <published>2024-03-13T13:20:19.000Z</published>
    <updated>2024-12-21T14:20:18.379Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1、GPU-的并行计算"><a href="#1、GPU-的并行计算" class="headerlink" title="1、GPU 的并行计算"></a>1、GPU 的并行计算</h2><p>我在<a class="link"   href="https://yanzhenjie.blog.csdn.net/article/details/136435820" >人工智能|各名称与概念之介绍<i class="fas fa-external-link-alt"></i></a>一文中提到过，深度学习是目前最主流的人工智能算法，从过程来看，它包括训练（Training）和推理（Inference）两个环节。</p><p>在训练环节，通过投喂大量的数据，训练出一个复杂的神经网络模型。在推理环节，利用训练好的模型，使用大量数据推理出各种结论。</p><p>训练环节由于涉及海量的训练数据和复杂的深度神经网络结构，所以计算规模非常庞大，对芯片的算力性能要求比较高。而推理环节，对简单指定的重复计算和实时性要求很高。模型所采用的具体算法，包括矩阵相乘、全连接层、卷积层、池化层、激活层、批归一化层和梯度运算等，分解为大量并行任务，可以有效缩短任务完成的时间。</p><p>总之，我想说明的是：<strong>神经网络模型的训练需要庞大的算力</strong>。那么它和 GPU 有什么关系呢？CPU 又在做什么？</p><p><strong>理解 CPU 的角色</strong>。如果我们把处理器看成是一个餐厅的话，CPU 就像一个拥有几十名高级厨师的全能型餐厅，它什么菜系的菜都能做。但是因为菜系众多、工序复杂和厨师较少，所以需要花费大量的时间协调和配菜，上菜的速度相对比较慢。</p><p><strong>理解 GPU 的角色</strong>。如果我们把处理器看成是一个餐厅的话，GPU 就像一个拥有千万名初级厨师的单一型餐厅，它只适合做某种指定菜系的菜。但是因为菜系单一、工序简单和厨师较多，所以仅需要少量时间通知任意一个厨师做菜，上菜速度反而快。</p><center><img     src="1.png"  ></img></br>（图一）</center><p>如图一所示，左侧为 CPU 角色，右侧为 GPU 角色。GPU 凭借自身强悍的并行计算能力以及内存带宽，可以很好地应对训练和推理任务，已经成为业界在深度学习领域的首选解决方案。如果对模型进行合理优化，一块 GPU 可以提供相当于几十块 CPU 的算力。</p><blockquote><p>本小节内容引用自（有大量修改，侵删）：<a class="link"   href="https://www.toutiao.com/article/7319434384707158562" >https://www.toutiao.com/article/7319434384707158562<i class="fas fa-external-link-alt"></i></a></p></blockquote><h2 id="2、CUDA-和-MPS"><a href="#2、CUDA-和-MPS" class="headerlink" title="2、CUDA 和 MPS"></a>2、CUDA 和 MPS</h2><p><code>torch.cuda</code>和<code>torch.mps</code>是 PyTorch 中的 2 个不同的模块，用于加速 PyTorch 训练。开发者在安装 PyTorch 时就需要注意安装正确的版本。我们进入官网后可以看到本地部署引导，并且会默认选中是和我们电脑的版本并给出安装命令或者安装包地址。</p><p>我们可以用鼠标点击选择自己想要安装的版本（系统、包管理、语言、计算平台）：</p><center><img     src="2.gif"  ></img></br>（图二）</center><p>比如本人使用的电脑为 MBP M2，可以选择稳定版 PyTorch、MacOS、Pip、Python、计算平台为 Default，出给出安装命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install torch torchvision torchaudio</span><br></pre></td></tr></table></figure><p>需要注意的是，MBP M 芯片系列的电脑，计算平台中 CUDA 会被画横线，不能选择，如果强行选择则不能安装。</p><ul><li>官网地址：<a class="link"   href="https://pytorch.org/get-started/locally/" >https://pytorch.org/get-started/locally/<i class="fas fa-external-link-alt"></i></a></li><li>存档版本：<a class="link"   href="https://pytorch.org/get-started/previous-versions" >https://pytorch.org/get-started/previous-versions<i class="fas fa-external-link-alt"></i></a></li></ul><h3 id="2-1、非-M-芯片验证-CUDA-可用性"><a href="#2-1、非-M-芯片验证-CUDA-可用性" class="headerlink" title="2.1、非 M 芯片验证 CUDA 可用性"></a>2.1、非 M 芯片验证 CUDA 可用性</h3><p>如果是非 M 系列芯片的电脑（OS X、Linux、Window），安装命令大概率是包含 cadu 的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch</span><br><span class="line"></span><br><span class="line">conda install pytorch torchvision torchaudio cudatoolkit -c pytorch</span><br></pre></td></tr></table></figure><p>此时可以直接验证 GPU 状态是否正常可用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line">torch.cuda.is_available()</span><br></pre></td></tr></table></figure><p>如果正常可用，将输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">True</span><br></pre></td></tr></table></figure><p>如果不正常，将输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">False</span><br></pre></td></tr></table></figure><p><code>torch.cuda</code>的详细 API 文档：<a class="link"   href="https://pytorch.org/docs/master/cuda.html" >https://pytorch.org/docs/master/cuda.html<i class="fas fa-external-link-alt"></i></a></p><h3 id="2-2、M-芯片验证-MPS-可用性"><a href="#2-2、M-芯片验证-MPS-可用性" class="headerlink" title="2.2、M 芯片验证 MPS 可用性"></a>2.2、M 芯片验证 MPS 可用性</h3><p>如果是 M 系列芯片，那么是无法安装 CUDA 的：</p><center><img     src="3.png"  ></img></br>（图三）</center><p>很早之前，PyTorch 是不支持 M 芯片的。我查了下相关信息，找到 soumith 在 PyTorch 项目下讨论过对 GPU 加速的支持：<a class="link"   href="https://github.com/pytorch/pytorch/issues/47702" >https://github.com/pytorch/pytorch/issues/47702<i class="fas fa-external-link-alt"></i></a>，2020 年 10 月的回答，有兴趣的读者可以看看。</p><p>在 2022 年 5 月 18 日，PyTorch 发布博客说 PyTorch v1.12 版本支持了 Mac OS 的 GPU 加速：<a class="link"   href="https://pytorch.org/blog/introducing-accelerated-pytorch-training-on-mac/" >https://pytorch.org/blog/introducing-accelerated-pytorch-training-on-mac/<i class="fas fa-external-link-alt"></i></a>。</p><p>苹果的 MPS（Metal Performance Shaders）扩展了 PyTorch 框架，可以作为 PyTorch 的后端加速 GPU 训练，它提供了在 Mac 上设置和运行操作的脚本和功能，MPS 通过针对每个 Metal GPU 系列的独特特性进行了微调的内核来优化计算性能。</p><p>如果你安装的 PyTorch 的版本是大于等于 v1.12 的，那么默认就支持加速 PyTorch 训练的。</p><p>此时可以直接验证 GPU 状态是否正常可用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line">torch.backends.mps.is_available()</span><br></pre></td></tr></table></figure><p>如果正常可用，此时将输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">True</span><br></pre></td></tr></table></figure><p>如果不正常，此时将输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">False</span><br></pre></td></tr></table></figure><p><code>torch.mps</code>的详细 API 文档：</p><ul><li><a class="link"   href="https://pytorch.org/docs/master/mps.html" >https://pytorch.org/docs/master/mps.html<i class="fas fa-external-link-alt"></i></a></li><li><a class="link"   href="https://pytorch.org/docs/master/notes/mps.html" >https://pytorch.org/docs/master/notes/mps.html<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="3、CPU-和-GPU-的切换"><a href="#3、CPU-和-GPU-的切换" class="headerlink" title="3、CPU 和 GPU 的切换"></a>3、CPU 和 GPU 的切换</h2><p>首先需要确定在设备上有 1 个或者多个 GPU 可用，在 2 中我们已经确认过了。现在我们需要将我们的数据转移到 GPU 可以看到的地方，是什么意思呢？我们知道 CPU 做计算靠的是 RAM，GPU 做计算靠的是 VRAM（Video Random Access Memory），VRAM 是 GPU 的专用内存。因此我们可以说把数据移动到 GPU 连接的内存，或者简称把数据移动到 GPU。</p><p>默认情况下，我们创建张量是在 CPU 设备上的（打印张量时，通过张量的属性可以看到）：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"></span><br><span class="line">x = torch.Tensor([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])</span><br><span class="line"><span class="built_in">print</span>(x, x.device)</span><br></pre></td></tr></table></figure><p>此时可以看到输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tensor([1., 2., 3.]) cpu</span><br></pre></td></tr></table></figure><p>以<code>torch.rand()</code>函数为例，如果我们事先知道此时 GPU 的可用状态，那么我们直接创建即可：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> torch.cuda.is_available():</span><br><span class="line">    my_device = torch.device(<span class="string">&#x27;cuda&#x27;</span>)</span><br><span class="line"><span class="keyword">elif</span> torch.backends.mps.is_available():</span><br><span class="line">    my_device = torch.device(<span class="string">&#x27;mps&#x27;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    my_device = torch.device(<span class="string">&#x27;cpu&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Device: &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(my_device))</span><br><span class="line"></span><br><span class="line">x = torch.rand(<span class="number">2</span>, <span class="number">2</span>, device=my_device)</span><br><span class="line"><span class="built_in">print</span>(x)</span><br></pre></td></tr></table></figure><p>如果 GPU 支持<code>cuda</code>，此时可以看到输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Device: cuda</span><br><span class="line">tensor([[0.0024, 0.6778],</span><br><span class="line">        [0.2441, 0.6812]], device=<span class="string">&#x27;cuda:0&#x27;</span>)</span><br></pre></td></tr></table></figure><p>如果 GPU 支持<code>mps</code>，此时可以看到输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Device: mps</span><br><span class="line">tensor([[0.5986, 0.4086],</span><br><span class="line">        [0.0624, 0.7131]], device=<span class="string">&#x27;mps:0&#x27;</span>)</span><br></pre></td></tr></table></figure><p>当然，每次写一堆<code>if else</code>会感觉比较费劲，可以用三元表达式简写它们：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">device_name = (</span><br><span class="line">    <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;mps&quot;</span></span><br><span class="line">    <span class="keyword">if</span> torch.backends.mps.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="built_in">print</span>(device_name)</span><br></pre></td></tr></table></figure><p>OK，现在我们可以使用<code>.to</code>函数让张量在 CPU 和 GPU 之间进行切换了：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先拿到处理器名称</span></span><br><span class="line">device_name = (</span><br><span class="line">    <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;mps&quot;</span></span><br><span class="line">    <span class="keyword">if</span> torch.backends.mps.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 声明处理器</span></span><br><span class="line">cpu_device = torch.device(<span class="string">&quot;cpu&quot;</span>)</span><br><span class="line">gpu_device = torch.device(device_name)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 声明张量</span></span><br><span class="line">x = torch.Tensor([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换到CPU</span></span><br><span class="line">x = x.to(cpu_device)</span><br><span class="line"><span class="built_in">print</span>(x, x.device)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换到GPU</span></span><br><span class="line">x = x.to(gpu_device)</span><br><span class="line"><span class="built_in">print</span>(x, x.device)</span><br></pre></td></tr></table></figure><p>此时可以看到输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tensor([1., 2., 3.]) cpu</span><br><span class="line">tensor([1., 2., 3.], device=<span class="string">&#x27;mps:0&#x27;</span>) mps:0</span><br></pre></td></tr></table></figure><p>PyTorch 官网一个很老的文档，但不及本文新：<a class="link"   href="https://pytorch.org/tutorials/beginner/introyt/tensors_deeper_tutorial.html#moving-to-gpu" >https://pytorch.org/tutorials/beginner/introyt/tensors_deeper_tutorial.html#moving-to-gpu<i class="fas fa-external-link-alt"></i></a></p><p>有兴趣一起交流学习的，可以加我的QQ群：874450808</p><p>本文完！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;1、GPU-的并行计算&quot;&gt;&lt;a href=&quot;#1、GPU-的并行计算&quot; class=&quot;headerlink&quot; title=&quot;1、GPU 的并行计算&quot;&gt;&lt;/a&gt;1、GPU 的并行计算&lt;/h2&gt;&lt;p&gt;我在&lt;a class=&quot;link&quot;   href=&quot;https://</summary>
      
    
    
    
    <category term="人工智能" scheme="https://www.yanzhenjie.com/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    
    
    <category term="PyTorch" scheme="https://www.yanzhenjie.com/tags/PyTorch/"/>
    
  </entry>
  
  <entry>
    <title>人工智能|各名称与概念认识</title>
    <link href="https://www.yanzhenjie.com/post/20240304/287dcf5e8988/"/>
    <id>https://www.yanzhenjie.com/post/20240304/287dcf5e8988/</id>
    <published>2024-03-04T13:22:23.000Z</published>
    <updated>2024-12-21T14:21:39.574Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>适莽苍者，三餐而反，腹犹果然；适百里者，宿舂粮；适千里者，三月聚粮。<br>——庄周《逍遥游》</p></blockquote><p>上文引用了战国中期先秦道家学派的代表人物庄子的逍遥游选段，翻译为白话文大致含义是：到近郊去的人，只带当天吃的三餐粮食，回来肚子还是饱饱的；到百里外的人，要用一整夜时间舂米准备干粮；到千里外的人，要用三个月来聚集粮草。</p><p>我相信，看到这篇文章的人，一定是致千里者。</p><p>我最早接触人工智能相关概念的时候大概是2019年，那时Google更新了TensorFlow，当时判断人工智能在未来肯定会越来越火，就建了个人工智能交流群，没几天500人的群就满员了。</p><p>于是，我们开始入门了。刚入门的时候遇到了很多与人工智能相关的概念，都不太了解什么意思，学习时脑袋就比较混乱，幸好我在CS领域有多年深耕经验，知道入门一个新领域时最好是对其有宏观的认识，然后再有计划的学习细分领域。这也是我发这篇文章的原因，希望这篇文章可以帮助到后来的入门者。</p><h2 id="1、人工智能相关概念"><a href="#1、人工智能相关概念" class="headerlink" title="1、人工智能相关概念"></a>1、人工智能相关概念</h2><p>关于人工智能的发展历史就不再介绍了，从人工智能的定义和历史发展来看，它是一门内容广泛的学科。因此，在日常交流中，大家常常存在将人工智能、机器学习、深度学习、监督学习、强化学习、主动学习等混用的情况。</p><p>实际上，机器学习是人工智能的一个分支，它能够从数据中自动分析获得规律，对未知数据进行预测。而人工神经网络则是人类模仿生物神经网络的结构和功能提供的一种计算模型，属于机器学习算法的一种。2012年，AlexNet获得ImageNet挑战赛冠军后，深度学习就成为了热门的机器学习算法。</p><h3 id="1-1、人工智能的几个重要概念"><a href="#1-1、人工智能的几个重要概念" class="headerlink" title="1.1、人工智能的几个重要概念"></a>1.1、人工智能的几个重要概念</h3><h4 id="1-1-1、监督学习"><a href="#1-1-1、监督学习" class="headerlink" title="1.1.1、监督学习"></a>1.1.1、监督学习</h4><p>监督学习的任务是学习一个模型，这个模型可以处理任意的一个输入，并且针对每个输入都可以映射输出一个预测结果，这里模型就相当于数学中一个函数，输入就相当于数学中的<strong>X</strong>，而预测的结果就相当于数学中的<strong>Y</strong>，对于每一个<strong>X</strong>，都可以通过一个映射函数映射出一个结果。</p><h4 id="1-1-2、非监督学习"><a href="#1-1-2、非监督学习" class="headerlink" title="1.1.2、非监督学习"></a>1.1.2、非监督学习</h4><p>非监督学习是指直接对没有标记的训练数据进行建模学习。注意，在这里的数据是没有标记的数据，与监督学习最基本的区别之一就是建模的数据是否有标签。例如聚类（将物理或抽象对象的集合分成由类似的对象组成的多个类的过程）就是一种典型的非监督学习，分类就是一种典型的监督学习。</p><h4 id="1-1-3、半监督学习"><a href="#1-1-3、半监督学习" class="headerlink" title="1.1.3、半监督学习"></a>1.1.3、半监督学习</h4><p>当有标记的数据很少、未被标记的数据很多，人工标记又比较昂贵时，可以根据一些条件（查询算法）查询一些数据，让专家进行标记，这是半监督学习与其他算法的本质区别。所以说对主动学习的研究主要是设计一种框架模型，运用新的查询算法查询需要专家来人工标注的数据，最后用查询到的样本训练分类模型来提高模型的精确度。</p><h4 id="1-1-4、主动学习"><a href="#1-1-4、主动学习" class="headerlink" title="1.1.4、主动学习"></a>1.1.4、主动学习</h4><p>当使用一些传统的监督学习方法做分类处理时，通常是训练样本的规模越大，分类的效果就越好。但是在现实中的很多场景中，标记样本的获取是比较困难的，因为这需要领域内的专家来进行人工标注，所花费的时间成本和经济成本都很大。而且，如果训练样本的规模过于庞大，则训练花费的时间也会比较多。那么问题来了：有没有一种有效办法，能够使用较少的训练样本来获得性能较好的分类器呢?答案是肯定的，主动学习（ActiveLearning）提供了这种可能，主动学习通过一定的算法查询出最有用的未标记样本，并交由专家进行标记，然后用查询到的样本训练分类模型来提高模型的精确度。</p><p>在人类的学习过程中，通常利用已有的经验来学习新的知识，又依靠获得的知识来总结和积累经验，经验与知识不断交互。同样，机器学习就是模拟人类学习的过程，利用已有的知识训练出模型去获取新的知识。并通过不断积累的信息去修正模型，以得到更加准确有用的新模型。不同于被动地接受知识，主动学习能够有选择性地获取知识。</p><h3 id="1-2、机器学习的分类"><a href="#1-2、机器学习的分类" class="headerlink" title="1.2、机器学习的分类"></a>1.2、机器学习的分类</h3><p>根据不同的划分角度，可以将机器学习划分为不同的类型。</p><h4 id="1-2-1、按任务类型划分"><a href="#1-2-1、按任务类型划分" class="headerlink" title="1.2.1、按任务类型划分"></a>1.2.1、按任务类型划分</h4><p>机器学习模型按任务类型可以分为回归模型、分类模型和结构化学习模型。具体说明如下：  </p><ol><li>回归模型：又叫预测模型，输出的是一个不能枚举的数值。  </li><li>分类模型：又分为二分类模型和多分类模型，常见的二分类问题有垃圾邮件过滤，常见的多分类问题有文档自动归类。  </li><li>结构化学习模型：此类型的输出不再是一个固定长度的值，如图片语义分析输出是图片的文字描述。</li></ol><h4 id="1-2-2、按方法划分"><a href="#1-2-2、按方法划分" class="headerlink" title="1.2.2、按方法划分"></a>1.2.2、按方法划分</h4><p>机器学习按方法可以分为线性模型和非线性模型，具体说明如下：  </p><ol><li>线性模型：虽然比较简单，但是其作用不可忽视，线性模型是非线性模型的基础，很多非线性模型都是在线性模型的基础上变换而来的。  </li><li>非线性模型：又可以分为传统机器学习模型（如SVM、KNN、决策树等）和深度学习模型。</li></ol><h4 id="1-2-3、按学习理论划分"><a href="#1-2-3、按学习理论划分" class="headerlink" title="1.2.3、按学习理论划分"></a>1.2.3、按学习理论划分</h4><p>机器学习模型可以分为<strong>有监督学习</strong>、<strong>半监督学习</strong>、<strong>无监督学习</strong>、<strong>迁移学习</strong>和<strong>强化学习</strong>，具体说明如下：  </p><ol><li>训练样本带有标签时是有监督学习。  </li><li>训练样本部分有标签、部分无标签时是半监督学习。  </li><li>训练样本全部无标签时是无监督学习。  </li><li>迁移学习就是把已经训练好的模型参数迁移到新的模型上，以帮助新模型训练。  </li><li>强化学习是一个学习最优策略（Policy，可以让本体（Agent）在特定环境（Environment）中，根据当前状态（State）做出行动（Action），从而获得最大回报（Reward），<strong>强化学习和有监督学习最大的不同</strong>是：每次的决定没有对与错，只是希望获得最多的累积奖励。</li></ol><h3 id="1-3、机器学习和深度学习"><a href="#1-3、机器学习和深度学习" class="headerlink" title="1.3、机器学习和深度学习"></a>1.3、机器学习和深度学习</h3><p>机器学习是一种实现人工智能的方法，而深度学习是一种实现机器学习的技术。深度学习本来并不是一种独立的学习方法，其本身也会用到有监督和无监督的学习方法来训练深度神经网络。但由于近几年该领域发展迅猛，一些特有的学习手段相继被提出（如残差网络等），因此越来越多的人将其单独看作一种学习的方法。</p><p>假设需要识别某个照片是狗还是猫，如果是用传统机器学习的方法，首先会定义一些特征，如有没有胡须、耳朵、鼻子和嘴巴的特征等。总之，首先要确定相应的“面部特征”作为机器学习的特征，以此将对象进行分类识别。而深度学习的方法则更进一步，它会自动找出这个分类问题所需要的重要特征，而传统机器学习则需要人工地给出特征。</p><p>我的理解是，其实深度学习并不是一个独立的算法，在训练神经网络的时候也通常会用到监督学习和无监督学习。但是由于一些独特的学习方法被提出，也可以把它看成是单独的一种学习算法。深度学习可以大致理解成包含多个隐含层的神经网络结构，深度学习的深指的就是隐藏层的深度。</p><h2 id="2、神经网络"><a href="#2、神经网络" class="headerlink" title="2、神经网络"></a>2、神经网络</h2><h3 id="2-1、CNN"><a href="#2-1、CNN" class="headerlink" title="2.1、CNN"></a>2.1、CNN</h3><p>CNN是卷积神经网络（Convolutional Neural Network）的缩写，是一种专门为处理具有网格结构的数据（如图像）而设计的深度学习架构。CNN在图像处理和计算机视觉领域特别成功，广泛应用于图像识别、物体检测、图像分类、图像分割、视频分析和自然语言处理等任务。</p><p>CNN的核心思想是利用卷积运算来自动和有效地提取图像中的局部特征，无需手动特征工程。卷积层通过滤波器（也叫卷积核或权重）在输入图像上滑动，计算局部区域的点积，并生成特征图（feature map）来表示特定的视觉特征，如边缘、角点或更复杂的纹理和形状。</p><p>CNN通常由以下几种类型的层组成：  </p><ol><li>卷积层（Convolutional Layer）：使用卷积核提取输入数据的特征。</li><li>激活层（Activation Layer）：通常是非线性激活函数，如ReLU，它增加了模型的非线性能力，使得网络能够捕捉更复杂的特征。</li><li>池化层（Pooling Layer）：采用下采样操作（如最大池化或平均池化）来减少特征图的空间尺寸，减少参数数量，以及增加模型的不变性特性。</li><li>全连接层（Fully Connected Layer）：通常放在网络的最后几层，这些层可将学习到的高层特征映射到最终的输出，例如分类的类别。</li><li>归一化层（Normalization Layer）：例如批归一化（Batch Normalization），用于调整神经网络中间层的激活值分布，以提高训练速度和稳定性。</li><li>丢弃层（Dropout Layer）：随机丢弃网络中的部分激活值，以避免过拟合。</li></ol><h3 id="2-2、RNN"><a href="#2-2、RNN" class="headerlink" title="2.2、RNN"></a>2.2、RNN</h3><p>RNN是循环神经网络（Recurrent Neural Network）的缩写，是一种专门设计来处理序列数据的人工神经网络。与传统的前馈神经网络不同，RNN能够处理任意长度的序列，因为它们具有内部状态（memory）来存储前面的信息。</p><p>RNN设计用于处理序列数据和时间序列数据，能够对序列中的元素进行建模，考虑到时间上的依赖关系。RNN的关键在于它们的循环连接，这使得网络能够将信息从前序步骤传递到当前步骤，从而保持对先前元素的“记忆”。RNN在自然语言处理（NLP）、语音识别、时间序列预测等任务中非常有效。</p><p>RNN的主要问题是长期依赖问题，即难以捕捉长序列中的依赖关系，这通常表现为梯度消失或梯度爆炸。为了解决这些问题，研究者开发了LSTM（长短期记忆）和GRU（门控循环单元）等更高级的循环神经网络变体。</p><h3 id="2-3、CNN与RNN的区别"><a href="#2-3、CNN与RNN的区别" class="headerlink" title="2.3、CNN与RNN的区别"></a>2.3、CNN与RNN的区别</h3><ol><li>数据类型：CNN主要用于图像数据，而RNN主要用于序列数据。</li><li>架构：CNN使用卷积层来提取空间特征，而RNN包含循环来处理时间序列。</li><li>应用场景：CNN通常用于图像和视频处理任务，如图像分类、对象检测和图像分割。RNN经常用于处理时间相关的任务，如语言模型、机器翻译、语音到文本转换。</li><li>记忆能力：CNN没有内部状态，不保存先前的输入信息，而RNN通过隐藏状态保持对过去信息的记忆。</li><li>参数共享：CNN通过滑动卷积核实现参数共享，而RNN在时间步之间共享参数。</li><li>变体和改进：CNN可以通过添加特殊层（如批归一化层）进一步优化，而RNN的改良通常涉及改变循环单元的结构（如使用LSTM或GRU）。</li></ol><h3 id="2-4、其他"><a href="#2-4、其他" class="headerlink" title="2.4、其他"></a>2.4、其他</h3><ol><li>多层感知器（MLP）:<ul><li>也称为密集或全连接网络，是神经网络的最基本形式。</li><li>每个神经元与前一层的所有神经元连接，没有循环或卷积结构。</li><li>主要用于表格数据或不需要捕获空间或时间依赖性的问题。</li></ul></li><li>自编码器（Autoencoders，AE）:<ul><li>用于无监督学习，主要用于降维和特征学习。</li><li>由编码器和解码器两部分组成，编码器将输入压缩成一个低维表示，解码器将该表示重构回原始输入。</li></ul></li><li>变分自编码器（Variational Autoencoders，VAE）:<ul><li>类似于传统自编码器，但它们产生的是输入数据的概率分布。</li><li>通常用于生成模型，可以生成新的、与训练数据类似的实例。</li></ul></li><li>生成对抗网络（Generative Adversarial Networks, GAN）:<ul><li>包含两个部分：一个生成器和一个鉴别器。</li><li>生成器尝试生成逼真的数据，鉴别器尝试区分真实数据和生成器生成的数据。</li><li>通常用于图像生成、风格转换等。</li></ul></li><li>长短期记忆网络（Long Short-Term Memory，LSTM）:<ul><li>一种特殊的RNN，通过引入门控制制解决了传统RNN的梯度消失问题。</li><li>在序列预测和自然语言处理领域得到了广泛应用。</li></ul></li><li>门控循环单元（Gated Recurrent Unit，GRU）:<ul><li>类似于LSTM，但结构上更简单，参数更少。</li><li>用门控机制来控制信息流，同样适用于处理序列数据。</li></ul></li><li>Transformers:<ul><li>侧重于自注意力机制（self-attention），能够处理整个输入序列并捕获远程依赖性。</li><li>在NLP领域取得了显著的成功，如BERT、GPT等模型都是基于Transformer架构。</li></ul></li><li>图神经网络（Graph Neural Networks，GNN）:<ul><li>用于处理图形数据，如社交网络、分子结构或任何其他非欧几里得数据。</li><li>能够捕获节点之间的连接关系和网络结构信息。</li></ul></li><li>卷积循环神经网络（Convolutional Recurrent Neural Networks, CRNN）:<ul><li>结合了CNN和RNN的特点，用于处理具有空间和时间维度的数据，如视频分析或图像序列。</li></ul></li><li>Capsule Networks (CapsNets):<ul><li>试图解决CNN在图像处理中的一些限制，如空间层次关系和视角不变性问题。</li><li>包含“胶囊”，其中包含了关于对象状态的信息，以及动态路由协议。</li></ul></li></ol><h2 id="3、模型格式转换"><a href="#3、模型格式转换" class="headerlink" title="3、模型格式转换"></a>3、模型格式转换</h2><h3 id="3-1、ONNX"><a href="#3-1、ONNX" class="headerlink" title="3.1、ONNX"></a>3.1、ONNX</h3><p>全称Open Neural Network Exchange，是微软主导的一个开放式深度学习神经网络模型格式。该格式旨在通过一个统一的模型标准，促进不同人工智能框架之间的互操作性，从而建立一个强大的人工智能生态。是一个用于表示深度学习模型的开放标准，它被设计成允许模型在不同的深度学习框架之间进行转换和互操作。支持TensorFlow、Keras、PyTorch、Caffe、CNTK、MXNet、Apache SINGA的转换，并且在与这些框架的集成中扮演“中间人”的角色。</p><p>官网：<a class="link"   href="https://onnx.ai/" >https://onnx.ai/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/onnx/onnx/" >https://github.com/onnx/onnx/<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="3-2、其他"><a href="#3-2、其他" class="headerlink" title="3.2、其他"></a>3.2、其他</h3><p>除了ONNX，目前没有广泛接受的、类似范围的通用标准，但有一些其他工具和库可以用于模型转换和优化：</p><ol><li>MMDNN（Model Management on Deep Neural Network）：<ul><li>由微软开发，它是一个跨框架的深度学习模型转换工具。</li><li>支持多种格式之间的转换，包括但不限于TensorFlow、Keras、PyTorch、Caffe和CNTK。</li></ul></li><li>TVM：<ul><li>一个开源机器学习编译器栈，用于从多种框架（包括PyTorch、TensorFlow、MXNet和Keras）编译和优化模型，以便在各种硬件上运行。</li><li>它提供了一个中间表示（IR）来优化模型，并可以将模型编译成高效的可执行代码。</li></ul></li><li>CoreMLTools：<ul><li>Apple提供的一套工具，用于将多种深度学习模型转换为苹果设备的Core ML格式。</li><li>支持TensorFlow、Keras、Caffe和其他框架的模型转换。</li></ul></li><li>TF-TRT (TensorFlow-TensorRT Integration)：<ul><li>TensorFlow和NVIDIA TensorRT的集成，用于加速TensorFlow模型在NVIDIA GPU上的推理。</li><li>它自动优化和转换TensorFlow图以利用TensorRT的优化。</li></ul></li><li>XLA (Accelerated Linear Algebra)：<ul><li>TensorFlow提供的一个编译器，可以优化TensorFlow模型的计算图，以提高执行效率和推理速度。</li></ul></li><li>OpenVINO Toolkit：<ul><li>由Intel推出，用于优化和部署深度学习模型。</li><li>支持多种格式的模型，包括ONNX、TensorFlow和Caffe，并且专门优化了在Intel硬件（如CPU、GPU和VPU）上运行。</li></ul></li></ol><p>尽管上述工具和库可能不如ONNX那样专注于模型格式的标准化，但它们提供了在不同框架和平台之间转换、优化和部署模型的功能。这些工具各有侧重点，选择时应考虑模型的原始框架、目标部署平台和性能要求。</p><h2 id="4、模型可视化工具"><a href="#4、模型可视化工具" class="headerlink" title="4、模型可视化工具"></a>4、模型可视化工具</h2><h3 id="4-1、Netron"><a href="#4-1、Netron" class="headerlink" title="4.1、Netron"></a>4.1、Netron</h3><p>在人工智能领域，Netron是一个图形化的工具，用于可视化和探索深度学习和机器学习模型的结构。它支持多种不同的模型文件格式，包括常见的框架如TensorFlow、Keras、PyTorch、Caffe、ONNX、CoreML等。</p><ul><li>模型可视化：通过Netron，用户可以直观地查看模型的层次结构、每层的参数和形状、连接模式等信息。</li><li>跨平台兼容性：Netron可以作为Web应用程序在浏览器中运行，也可以作为桌面应用程序在Windows、macOS和Linux上运行。</li><li>简单易用：用户只需将模型文件拖放到Netron中，就可以查看模型的结构。没有复杂的安装或配置过程。</li><li>模型探索：Netron允许用户交互式地探索模型的细节，例如点击不同的层来查看其属性和参数。</li><li>模型共享：由于Netron提供了图形化的模型表示，它可以帮助团队成员或合作伙伴之间更容易地讨论和共享模型设计。</li><li>无需运行模型：Netron用于静态分析，因此不需要运行模型或安装深度学习框架。</li></ul><p>开源地址：<a class="link"   href="https://github.com/lutzroeder/netron/" >https://github.com/lutzroeder/netron/<i class="fas fa-external-link-alt"></i></a><br>软件下载：<a class="link"   href="https://github.com/lutzroeder/netron/releases" >https://github.com/lutzroeder/netron/releases<i class="fas fa-external-link-alt"></i></a><br>网页版：<a class="link"   href="https://netron.app/" >https://netron.app/<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="4-2、其他"><a href="#4-2、其他" class="headerlink" title="4.2、其他"></a>4.2、其他</h3><ol><li>TensorBoard：<ul><li>TensorFlow提供的一个可视化工具，可以展示模型的计算图，监控训练过程中的各种指标，如损失和准确率，并分析模型的参数和性能。</li><li>支持标量、图像、音频、直方图和嵌入向量等数据的可视化。</li></ul></li><li>TensorWatch：<ul><li>微软推出的一个灵活的可视化工具，主要用于PyTorch。</li><li>支持实时的数据流可视化，可以在Jupyter Notebook中使用，也可以创建独立的实时图表。</li></ul></li><li>Visdom：<ul><li>由Facebook研究团队开发的一个灵活的可视化工具，可以用于创建、组织和分享实时数据的可视化。</li><li>支持多种数据类型的可视化，包括数值、图像、文本、音频和视频数据。</li></ul></li><li>Weights &amp; Biases：<ul><li>一个机器学习实验跟踪工具，可以帮助记录和可视化模型的训练过程。</li><li>提供了一个在线平台，适用于多种深度学习框架，支持超参数优化、模型版本控制和协作。</li></ul></li><li>What-If Tool：<ul><li>一个交互式可视化工具，用于分析机器学习模型的行为，尤其是在解释模型预测和公平性方面。</li><li>可以与TensorBoard配合使用，并支持多种数据格式和模型类型。</li></ul></li><li>PlotNeuralNet：<ul><li>一个LaTeX代码库，用于从代码中生成卷积神经网络的高质量架构图。</li><li>需要LaTeX环境来编译成图形，并允许用户自定义模型的外观和布局。</li></ul></li><li>NN-SVG：<ul><li>一个在线工具，用于快速生成神经网络架构的SVG图像。</li><li>用户可以调整层数、神经元数量和其他属性，然后下载生成的图像。</li></ul></li></ol><h2 id="5、深度学习框架"><a href="#5、深度学习框架" class="headerlink" title="5、深度学习框架"></a>5、深度学习框架</h2><p>本文介绍了目前比较主流的、我个人比较感兴趣一些深度学习框架。</p><h3 id="5-1、TensorFlow"><a href="#5-1、TensorFlow" class="headerlink" title="5.1、TensorFlow"></a>5.1、TensorFlow</h3><ul><li>发展者与社区： 由Google的Google Brain团队开发，有着非常活跃的社区和广泛的工业支持。</li><li>接口与使用： 提供了Python、C++等多种语言的API，以及高层API如tf.keras，适合各种用户需求。</li><li>设计和特点： 初版基于静态计算图，但从TensorFlow 2.0开始，默认支持动态计算图（Eager Execution）。</li><li>扩展性和部署： 支持多种硬件平台，包括GPU和TPU，提供了TensorFlow Lite供移动和嵌入式设备使用。</li><li>应用范围： 适用于广泛的机器学习任务，从研究到企业级生产均有广泛应用。</li></ul><p>TensorFlow官网：<a class="link"   href="https://www.tensorflow.org/" >https://www.tensorflow.org/<i class="fas fa-external-link-alt"></i></a><br>TensorFlow Lite官网：<a class="link"   href="https://www.tensorflow.org/lite" >https://www.tensorflow.org/lite<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/tensorflow/tensorflow" >https://github.com/tensorflow/tensorflow<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="5-2、Keras"><a href="#5-2、Keras" class="headerlink" title="5.2、Keras"></a>5.2、Keras</h3><ul><li>发展者与社区： 最初由François Chollet单独开发，现在是TensorFlow的官方高级API。</li><li>接口与使用： 提供了高度抽象化的API，使得构建和训练模型变得简单快捷，适合初学者和快速原型设计。</li><li>设计和特点： 作为TensorFlow的一部分，与TensorFlow紧密集成，同时保持了独立性和模块化。</li><li>扩展性和部署： 作为TensorFlow的一部分，享受TensorFlow的扩展性和部署能力。</li><li>应用范围： Keras非常适合教学和快速开发，但对于一些需要高度定制化的复杂模型，可能会使用TensorFlow的低级API。</li></ul><p>Keras在2017年被集成进TensorFlow，在TensorFlow 2.0版本中，Keras被正式确立为TensorFlow的高级API，即tf.keras。</p><p>官网：<a class="link"   href="https://keras.io/" >https://keras.io/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/keras-team/keras" >https://github.com/keras-team/keras<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="5-3、PyTorch"><a href="#5-3、PyTorch" class="headerlink" title="5.3、PyTorch"></a>5.3、PyTorch</h3><ul><li>发展者与社区： 由Facebook的AI研究团队开发，并且在学术界有很大影响力和强大的社区。</li><li>接口与使用： 提供了以Python为中心的API，充分利用了Python的动态特性，使原型设计和实验更加自然。</li><li>设计和特点： 采用动态计算图，即定义即运行（define-by-run）方式，非常适合研究和动态变化的模型。</li><li>扩展性和部署： 可以导出为ONNX格式，与其他工具配合实现跨平台部署。有一定的移动端支持。</li><li>应用范围： 在学术研究中非常受欢迎，也适用于工业应用，尤其是在快速原型设计和迭代中。</li></ul><p>官网：<a class="link"   href="https://pytorch.org/" >https://pytorch.org/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/pytorch/pytorch" >https://github.com/pytorch/pytorch<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="5-4、Caffe"><a href="#5-4、Caffe" class="headerlink" title="5.4、Caffe"></a>5.4、Caffe</h3><ul><li>发展者与社区： 由加州大学伯克利分校的BVLC团队开发，社区相对于TensorFlow和PyTorch较小。</li><li>接口与使用： 主要使用配置文件定义模型结构，同时提供了Python和C++的API，但没有TensorFlow和PyTorch那么灵活。</li><li>设计和特点： 高性能的前向传播和速度优化，适合于图像分类和其他视觉任务。</li><li>扩展性和部署： 主要优化了在GPU上的部署，但在移动端和嵌入式设备支持方面不如TensorFlow和PyTorch。</li><li>应用范围： 因其高效的性能，Caffe经常用于工业产品和科研中的模型训练和部署，尤其是计算机视觉任务。</li></ul><p>官网：<a class="link"   href="https://caffe.berkeleyvision.org/" >https://caffe.berkeleyvision.org/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/BVLC/caffe" >https://github.com/BVLC/caffe<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="5-5、Core-ML"><a href="#5-5、Core-ML" class="headerlink" title="5.5、Core ML"></a>5.5、Core ML</h3><p>Core ML 并不是一个开源项目，它是苹果公司开发的专有框架，用于在 iOS、macOS、tvOS 和 watchOS 上运行机器学习模型。由于它是专有的，没有公开的源代码可供下载或修改。</p><p>苹果公司的 Core ML 官方主页提供了关于 Core ML 的详细信息和文档，包括如何开始使用 Core ML、如何转换和训练模型、以及 Core ML 支持的模型格式等。</p><p>官网：<a class="link"   href="https://developer.apple.com/machine-learning/" >https://developer.apple.com/machine-learning/<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="5-6、总结深度学习框架"><a href="#5-6、总结深度学习框架" class="headerlink" title="5.6、总结深度学习框架"></a>5.6、总结深度学习框架</h3><p>总的来说，我个人比较推荐TensorFlow和PyTorch，它们都是完整的深度学习框架，支持广泛的应用，并且它们的社区和工具都在不断发展。Caffe在某些特定的领域如计算机视觉中仍然是一个高效可靠的选择，尽管它的流行度可能不如前两者。而Keras提供了一个用户友好的接口，使得深度学习更加容易上手，是学习和快速开发的首选。</p><h2 id="6、国内深度学习推理引擎"><a href="#6、国内深度学习推理引擎" class="headerlink" title="6、国内深度学习推理引擎"></a>6、国内深度学习推理引擎</h2><p>主要介绍几个国内开源的深度学习推理引擎。</p><h3 id="6-1、MNN"><a href="#6-1、MNN" class="headerlink" title="6.1、MNN"></a>6.1、MNN</h3><p>MNN（Mobile Neural Network）由阿里巴巴出品，是一个轻量级、高性能的深度学习框架，<strong>专门针对移动端和边缘设备</strong>设计。MNN的目标是帮助开发者和企业能够<strong>在移动设备上高效地部署</strong>和运行深度学习模型，特别是在资源有限的环境下。</p><ul><li>轻量化：MNN专为移动设备优化，拥有较小的运行时库，能够减少应用的资源消耗。</li><li>跨平台支持：MNN支持包括Android、iOS以及Linux等多个平台，同时支持ARM、x86、MIPS等不同架构的处理器。</li><li>高性能：MNN针对移动端处理器进行了深度优化，包括对ARM CPU的NEON指令集优化、对GPU的OpenCL和Metal优化，以及对NPU（神经网络处理单元）的适配，可充分利用设备的计算资源。</li><li>易用性：MNN提供了简洁的API，便于开发者快速集成和使用。它支持ONNX、Caffe、TensorFlow等多种模型格式，方便开发者导入和转换已有模型。</li><li>多后端支持：MNN可以根据不同的硬件条件自动选择最佳的计算后端执行计算任务，包括CPU、GPU、NPU等。</li><li>自动调度和内存复用：MNN内部实现了智能的算子调度和内存管理，减少内存占用和计算延迟。</li><li>自定义算子：如果遇到MNN不支持的算子，开发者还可以自定义算子以满足特殊需求。</li></ul><p>官网：<a class="link"   href="https://www.mnn.zone/" >https://www.mnn.zone/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/alibaba/MNN" >https://github.com/alibaba/MNN<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="6-2、NCNN"><a href="#6-2、NCNN" class="headerlink" title="6.2、NCNN"></a>6.2、NCNN</h3><p>NCNN（Neural Network Inference Framework）由腾讯出品，是腾讯优图实验室开发的高性能神经网络前向推理框架，<strong>专为移动端和边缘设备设计</strong>，具有轻量级和高效率的特点。</p><ul><li>轻量级：NCNN 设计目标是保持足够的轻量级，以便于在移动设备上快速部署和执行。</li><li>高性能：它通过优化执行速度来尽可能利用移动设备的计算资源，包括对 ARM CPU 的 NEON 指令集、Vulkan 计算着色器、OpenCL 和多线程的支持。</li><li>不依赖第三方库：NCNN 尽量减少依赖，不需要依赖 BLAS 或 CNN 等第三方库，这使得在移动端部署时更加简洁和方便。</li><li>支持多种模型格式：提供了工具来转换包括 Caffe、ONNX、PyTorch 和 TensorFlow 在内的多种模型到 NCNN 的格式。</li><li>易于集成：NCNN 提供了简单而直观的 API，使得将预训练模型集成到移动应用变得非常简单。</li><li>跨平台支持：支持在 Android、iOS、Windows 和 Linux 等多种平台上运行。</li></ul><p>开源地址：<a class="link"   href="https://github.com/Tencent/ncnn" >https://github.com/Tencent/ncnn<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="6-3、MindSpore"><a href="#6-3、MindSpore" class="headerlink" title="6.3、MindSpore"></a>6.3、MindSpore</h3><p>MindSpore是华为推出的一个全场景深度学习框架，<strong>移动端部署需要使用MindSpore Lite</strong>。</p><ul><li>轻量级：相较于完整版的MindSpore，Lite版本更小巧，适用于计算能力和存储空间受限的移动设备。</li><li>跨平台支持：支持Android和iOS等移动操作系统，方便开发者将AI模型部署到各种智能手机和平板电脑上。</li><li>优化的性能：针对移动和端侧设备进行了性能优化，以提高运算速度和效率，减少功耗。</li><li>易用的工具链：提供工具链支持，包括模型转换工具，可以将MindSpore或其他框架（如ONNX）的模型转换为MindSpore Lite模型。</li><li>丰富的算子库：包含了丰富的算子（操作），以支持多种AI场景下的计算需求。</li></ul><p>MindSpore官网：<a class="link"   href="https://www.mindspore.cn/" >https://www.mindspore.cn/<i class="fas fa-external-link-alt"></i></a><br>MindSpore Lite官网：<a class="link"   href="https://www.mindspore.cn/lite" >https://www.mindspore.cn/lite<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/mindspore-ai/mindspore" >https://github.com/mindspore-ai/mindspore<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="6-4、mace"><a href="#6-4、mace" class="headerlink" title="6.4、mace"></a>6.4、mace</h3><p>MACE（Mobile AI Compute Engine）是小米开源的一个深度学习推理框架，专门设计用于<strong>在移动端和嵌入式设备上优化和运行</strong>机器学习模型。MACE支持多种平台，包括Android和iOS设备，以及支持ARM架构的Linux设备。</p><ul><li>跨平台：提供对Android和iOS操作系统的支持，使得开发者能够轻松地将训练好的模型部署到多种移动设备上。</li><li>多后端支持：支持在CPU、GPU和DSP等多种计算单元上运行模型，利用OpenCL和OpenGL等技术实现跨设备的通用性和高性能。</li><li>模型转换工具：包含模型转换器，可以将Caffe、TensorFlow、ONNX等格式的模型转换为MACE支持的格式。</li><li>优化的性能：针对移动设备的计算资源进行优化，提高模型运行效率，减少延迟和功耗。</li><li>易用性：提供了易于使用的接口和详细的文档，帮助开发者快速上手并部署他们的模型。</li><li>算子库：包含了丰富的预定义算子（operations），满足常见深度学习模型的需求。</li></ul><p>官网：<a class="link"   href="https://mace.readthedocs.io/" >https://mace.readthedocs.io/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/XiaoMi/mace" >https://github.com/XiaoMi/mace<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="6-5、PaddlePaddle"><a href="#6-5、PaddlePaddle" class="headerlink" title="6.5、PaddlePaddle"></a>6.5、PaddlePaddle</h3><p>PaddlePaddle是由百度推出的开源深度学习平台，支持多种深度学习模型，并且特别优化了在百度大规模应用中的一些实用功能。</p><ul><li>跨平台兼容性：支持多种操作系统，包括<strong>Android</strong>、<strong>iOS</strong>、Linux等，并支持ARM、X86、OpenCL、Metal、CUDA等多种后端。</li><li>轻量化设计：为了适应移动端和嵌入式设备的性能和存储限制，PaddlePaddle Lite优化了内存占用和运行速度，使之成为一个轻量级推理引擎。</li><li>优化的性能：通过内核级优化、算子融合、内存复用等技术实现了高性能的深度学习模型推理。</li><li>易于集成：提供了简洁的API接口，便于将PaddlePaddle Lite集成到移动端应用中。</li><li>模型转换工具：配备了模型优化工具，可以将PaddlePaddle格式的模型转换为PaddlePaddle Lite支持的格式，并对模型进行优化以提高在端侧设备上的性能。</li></ul><p>官网：<a class="link"   href="https://www.paddlepaddle.org.cn/" >https://www.paddlepaddle.org.cn/<i class="fas fa-external-link-alt"></i></a><br>PaddlePaddle开源地址：<a class="link"   href="https://github.com/PaddlePaddle" >https://github.com/PaddlePaddle<i class="fas fa-external-link-alt"></i></a><br>Paddle-Lite开源地址：<a class="link"   href="https://github.com/PaddlePaddle/Paddle-Lite" >https://github.com/PaddlePaddle/Paddle-Lite<i class="fas fa-external-link-alt"></i></a></p><h3 id="6-6、MegEngine"><a href="#6-6、MegEngine" class="headerlink" title="6.6、MegEngine"></a>6.6、MegEngine</h3><p>MegEngine是旷视公司自主研发的深度学习框架，于2020年3月25日全面开源。MegEngine 致力于提供高性能和易用性，支持广泛的AI应用，从图像和视频分析到语音识别和自然语言处理。</p><ul><li>易用性：提供了 Python API，使得构建和训练模型的过程变得简单直观。</li><li>高性能：通过高效的张量操作和自动微分系统，以及优化后的内存使用和计算图执行，MegEngine 旨在提供高效的模型训练和推理。</li><li>分布式训练：支持多GPU和跨节点的分布式训练，使得处理大规模数据集和复杂模型成为可能。</li><li>模型转换和优化：MegEngine 提供了一系列用于模型优化和量化的工具，以减小模型大小并加快推理速度，适应各种计算环境。</li></ul><p>官网：<a class="link"   href="https://www.megengine.org.cn/" >https://www.megengine.org.cn/<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/MegEngine/MegEngine" >https://github.com/MegEngine/MegEngine<i class="fas fa-external-link-alt"></i></a>  </p><h3 id="6-7、OneFlow"><a href="#6-7、OneFlow" class="headerlink" title="6.7、OneFlow"></a>6.7、OneFlow</h3><p>OneFlow 是OneFlow Inc.（一流科技有限公司）开发病开源的，OneFlow 旨在为用户提供易用、高效和可扩展的深度学习工具，特别强调在大规模分布式环境中的性能和灵活性。</p><ul><li>性能优化：OneFlow 设计了新颖的底层框架，用于提高计算和内存利用率，尤其关注大规模分布式训练的性能优化。</li><li>易用性：提供了类似于 PyTorch 的接口，使用户可以轻松迁移现有的 PyTorch 代码，同时保持性能优势。</li><li>动静态图兼容：支持动态图和静态图两种编程范式，用户可以根据需求灵活选择。</li><li>分布式训练：内置了先进的分布式训练策略，支持各种分布式训练场景，包括数据并行、模型并行和流水线并行。</li><li>自动混合精度：支持自动混合精度训练，能够在保持模型精度的同时减少内存消耗和提高训练速度。</li></ul><p>官网：<a class="link"   href="https://oneflow.org/" >https://oneflow.org<i class="fas fa-external-link-alt"></i></a><br>开源地址：<a class="link"   href="https://github.com/Oneflow-Inc/oneflow" >https://github.com/Oneflow-Inc/oneflow<i class="fas fa-external-link-alt"></i></a>  </p><p>本文完。</p><blockquote><p>特别说明：本文部分内容来自AI大模型，如有错误请评论指出，不胜感激！</p></blockquote><hr><p>本文参考了以下文献：</p><ul><li>《TensorFlow Lite移动端深度学习》——朱元涛</li><li>《PyTorch神经网络实战》——丛晓峰、彭程威、章军</li></ul>]]></content>
    
    
    <summary type="html">适莽苍者，三餐而反，腹犹果然；适百里者，宿舂粮；适千里者，三月聚粮。上文为战国中期先秦道家学派的代表人物庄子的逍遥游选段，翻译为白话文大致含义是：到近郊去的人，只带当天吃的三餐粮食，回来肚子还是饱饱的；到百里外的人，要用一整夜时间舂米准备干粮；到千里外的人，要用三个月来聚集粮草。</summary>
    
    
    
    <category term="人工智能" scheme="https://www.yanzhenjie.com/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    
    
    <category term="PyTorch" scheme="https://www.yanzhenjie.com/tags/PyTorch/"/>
    
    <category term="CNN" scheme="https://www.yanzhenjie.com/tags/CNN/"/>
    
    <category term="RNN" scheme="https://www.yanzhenjie.com/tags/RNN/"/>
    
    <category term="TensorFlow" scheme="https://www.yanzhenjie.com/tags/TensorFlow/"/>
    
  </entry>
  
</feed>
