开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

使用 tiktoken 计算 token 数量 什么是tik

发表于 2024-04-25

什么是tiktoken

iktoken是OpenAI开发的一种BPE分词器。

给定一段文本字符串(例如,"tiktoken is great!")和一种编码方式(例如,"cl100k_base"),分词器可以将文本字符串切分成一系列的token(例如,["t", "ik", "token", " is", " great", "!"])。

tiktoken解决了什么问题

将文本字符串切分成token非常有用,因为GPT模型看到的文本就是以token的形式呈现的。

知道一段文本字符串中有多少个token可以告诉你(a)这个字符串是否对于文本模型来说太长了而无法处理,以及(b)一个OpenAI API调用的费用是多少(因为使用量是按照token计价的)。

编码方式

编码方式规定了如何将文本转换成token。不同的模型使用不同的编码方式。

tiktoken支持OpenAI模型使用的三种编码方式:

编码名称 OpenAI模型
cl100k_base gpt-4, gpt-3.5-turbo, text-embedding-ada-002
p50k_base Codex模型, text-davinci-002, text-davinci-003
r50k_base (或 gpt2) 像 davinci 这样的GPT-3模型

如何获取模型的编码方式

你可以使用 tiktoken.encoding_for_model() 获取一个模型的编码方式,

如下所示:

1
python复制代码encoding = tiktoken.encoding_for_model('gpt-3.5-turbo')

注意,p50k_base 与 r50k_base 有很大的重叠,对于非代码应用,它们通常会产生相同的token。

在英语中,token的长度通常在一个字符到一个单词之间变化(例如,"t" 或 " great"),尽管在某些语言中,token可以比一个字符短或比一个单词长。空格通常与单词的开头一起分组(例如," is" 而不是 "is " 或 " "+"is")。你可以快速在 OpenAI分词器 检查一段字符串如何被分词。

image.png

安装 tiktoken

1
css复制代码pip install --upgrade tiktoken

示例-编码 encode 将字符串转换成token ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ini复制代码import tiktoken

# 第一次运行时,它将需要互联网连接进行下载。后续运行不需要互联网连接。
encoding = tiktoken.get_encoding("cl100k_base")


# 使用`tiktoken.encoding_for_model()`函数可以自动加载给定模型名称的正确编码。
# encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")

# 实际进行编码
# 通过计算`.encode()`返回的列表的长度来统计token数量。
encoding.encode("tiktoken is great!")

# The `.encode()` method converts a text string into a list of token integers.

输出 [83, 1609, 5963, 374, 2294, 0] 返回token ID 这边用了6个token

示例-解码 encode 将token ID 还原成 字符串

1
2
3
4
5
6
7
8
ini复制代码
import tiktoken

# 第一次运行时,它将需要互联网连接进行下载。后续运行不需要互联网连接。
encoding = tiktoken.get_encoding("cl100k_base")


print(encoding.decode([83, 1609, 5963, 374, 2294, 0]))

输出 tiktoken is great!

对比不同的编码方式

不同的编码方式在分割单词、处理空格和非英文字符方面存在差异。通过上述方法,我们可以比较几个示例字符串在不同的编码方式下的表现。

image.png

统计completions API调用时的token数

ChatGPT模型,如gpt-3.5-turbo和gpt-4,与旧的完成模型一样使用token,但由于其基于消息的格式,很难准确计算对话中将使用多少个token。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
python复制代码# 定义函数 num_tokens_from_messages,该函数返回由一组消息所使用的token数。
def num_tokens_from_messages(messages, model="gpt-3.5-turbo"):
"""Return the number of tokens used by a list of messages."""
# 尝试获取模型的编码
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
# 如果模型没有找到,使用 cl100k_base 编码并给出警告
print("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")

num_tokens = 0
# 计算每条消息的token数
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3 # 每条回复都以助手为首
return num_tokens

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从零训练Bert架构大模型 第一部分 tokenizer分词

发表于 2024-04-25

本文章不是模型微调,是从零构架大模型,是基于Bert架构的。
本文主要分两块,一个是tokenizer的构建。二是模型的构建和训练

第一部分 tokenizer分词器训练

bert的编码用的是WordPiece.

分词器训练分4个步骤,1:normalize; 2:pre_tokenizer预分词; 3:model; 4:post_processor后处理

step1 导包

1
2
3
4
5
6
pyton复制代码from tokenizers import Tokenizer,processors
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.normalizers import BertNormalizer
from tokenizers.pre_tokenizers import BertPreTokenizer
from tokenizers.decoders import WordPiece as WordPieceDecoder

step2 模型初始化,dataset(本文用的是文本)初始化

1
2
python复制代码tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
files = ["/sanguo.txt"] #你的训练数据,此处用的是三国演义

step3 normalize

1
python复制代码tokenizer.normalizer = BertNormalizer(lowercase=True)

step4 预分词

1
python复制代码tokenizer.pre_tokenizer = BertPreTokenizer()

step 5 添加special_token并模型训练

1
2
3
python复制代码special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = WordPieceTrainer(vocab_size=50000, show_progress=True, special_tokens=special_tokens)
tokenizer.train(files, trainer)

step 6 后处理及加入解码器

1
2
3
4
5
6
7
8
python复制代码cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
tokenizer.post_processor = processors.TemplateProcessing(
single=f"[CLS]:0 $A:0 [SEP]:0",
pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
tokenizer.decoder = WordPieceDecoder(prefix="##")

step 7 模型保存

1
python复制代码tokenizer.save("tokenizer.json")

如果要在Transformers中使用这个分词器,我们需要将它包装在一个PreTrainedTokenizerFast中。在这我们使用特定的标记器类BertTokenizerFast

1
2
3
4
python复制代码from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object = tokenizer)
wrapped_tokenizer.save_pretrained("./bert")

./bert文件夹下,新增了如下的文件

image.png

到此,分词器训练成功,我们的第一步完成,接下来是第二步,模型训练

第二部分 模型训练

step 1 导包

1
python复制代码from transformers import BertConfig,BertLMHeadModel,BertTokenizer,LineByLineTextDataset,DataCollatorForLanguageModeling,Trainer, TrainingArguments

step 2 加载第一部分训练的分词器

1
python复制代码tokenizer = BertTokenizer.from_pretrained("./bert")

step 3 模型配置

1
2
3
4
5
python复制代码config = BertConfig(
vocab_size=tokenizer.vocab_size,
is_decoder=True
)
model = BertLMHeadModel(config)

step 4 训练数据加载及处理

1
2
3
4
5
6
7
8
9
10
11
python复制代码dataset = LineByLineTextDataset(
tokenizer=tokenizer,
file_path="./sanguo.txt", # 你的训练数据
block_size=32
)

data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False,
mlm_probability=0.15
)

step 5 训练参数,训练器等设定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码training_args = TrainingArguments(
output_dir="./output",
overwrite_output_dir=True,
num_train_epochs=20,
per_device_train_batch_size=16,
save_steps=2000,
save_total_limit=2
)
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=dataset
)

trainer.train()

step 6 模型保存

1
python复制代码model.save_pretrained("./bert")

训练完毕后,./bert文件夹下新增了如下文件

image.png

到此,我们已经构建并训练完成bert大模型了,我们推理一下看看

模型推理测试

1
2
3
4
5
6
python复制代码from transformers import pipeline, set_seed

generator = pipeline("text-generation", model="./bert_model")
set_seed(42)
txt = generator("吕布", max_length=50)
print(txt)

image.png

再来一个

1
2
python复制代码txt = generator("接着奏乐", max_length=50)
print(txt)

image.png

看起来不错,比我参考的gpt2的强点,可能bert架构在小训练量的情况比gpt架构好点吧。。
具体代码,我会放到github上 ,地址davidhandsome86 (github.com)希望和感兴趣的小伙伴多沟通交流。。

参考

仅用61行代码,你也能从零训练大模型 - 知乎 (zhihu.com)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Llama3本地运行:无需编程,只需鼠标点点点 本文简介 安

发表于 2024-04-25

本文简介

点赞 + 关注 + 收藏 = 学会了

最近几天AI圈又沸腾起来了,原因是 Meta(原Facebook)开源了 Llama 3 8B与70B两款不同规模的模型。据 Meta 透露,这个模型可是用了 24576 个 GPU 训练出来的。

重点是开源,真的鲨风了!

那作为普通人,我们可以在自己电脑用 Llama 3 吗?这肯定是可以的,而且还不需要你懂编程,只要用鼠标点来点去就能在本地跑起来,断网情况下都能用。

安装方法

要在自己电脑使用 Llama 3 真的超级简单,只需2步。

  1. 安装 GPT4All 客户端。
  2. 下载 Llama 3 模型。

第1步:下载 GPT4All

下载 GPT4All 客户端,地址放这里:gpt4all.io/index.html

01.png

根据你的系统选择安装包。下载完成后打开安装包,一路点击“下一步”即可,非常简单~

第2步:下载 Llama 3 模型

安装好 GPT4All 后,打开它。

02.png

第一次打开因为你没有下载过任何模型,所以它会弹出这个弹窗让你选一个来下载。

这里选择第一个 Llama 3 Instruct ,然后点击右侧的 Download 按钮,等待它下载完就能用了。这个模型 4.34GB,在 Mac 上使用还是挺心疼的。

用起来!

模型下载完,在界面的顶部选择它,然后就可以开始聊天了。

03.png

但 Llama 3 现阶段对中文的适配不是那么友好,需要你先提醒它使用中文再和它交流。

04.png

05.png

当然,你也可以搜索 Llama3,下载个中文版。

06.png
你还可以搜索其他模型,根据你自己电脑配置去选择合适的模型即可。


IMG_2756.GIF

生在这个时代,能感受到AI带来的便利真的很爽。但AI的发展速度真的太快了,以前做切图仔的时候觉得学习速度跟不上别人造轮的速度,现在感觉卖AI课程的人可能都跟不上AI的发展速度了。说起AI卖课的,顺手推一篇之前写过的文章:《AI副业最赚钱的应该就是卖课了吧》。

点赞 + 关注 + 收藏 = 学会了

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

手把手教你轻松本地部署Llama3,个人电脑也能极速运行

发表于 2024-04-25

🧙‍♂️ 诸位好,吾乃斜杠君,编程界之翘楚,代码之大师。算法如流水,逻辑如棋局。

☘️ 吾之文章,不以繁复之言,惑汝耳目;但以浅显之语,引汝入胜。

☝ 若此文对阁下有所裨益,敬请♥️-点赞⭐ -收藏⏰ -关注,不胜感激。

大家好,我是斜杠君。今天手把手教大家如何在本地部署Llama3,打造一个属于自己的本地大模型助手。

在教大家部署Llama3之前,先要带大家了解一个概念:Ollama。

Ollama是什么?

Ollama是一个用于在本地计算机上运行大型语言模型(LLMs)的命令行工具。它允许用户下载并本地运行像Llama 3、Code Llama等模型,并支持自定义和创建自己的模型。

Ollama是免费开源的项目,支持macOS和Linux操作系统和Windows系统。它还提供了官方的Docker镜像,使用户可以通过Docker容器部署大型语言模型,确保所有与模型的交互都在本地进行,无需将私有数据发送到第三方服务。

安装

理解了Ollama是什么之后,让我们到官网下载Ollama程序吧~

**官网下载页面:**ollama.com/download

因为我的电脑是windows, 所以这里我下载Windows这个版本。

下载好了,程序大小一共是212M。

下载好后,双击点开始安装:

默认安装到C盘。PS:很奇怪为什么没给个自定义安装目录的选项 :(

安装完成后,Win+R键调出运行窗口:

在窗口中键入cmd调出控制台,在控制台中运行命令:ollama run llama3

这时就开始下载模型了。

等待一会儿,安装完成。

下载的文件在下面这个目录:

测试

先测试一下,回答怎么样?

再来测试一下中文。

测试结果回答逻辑是没什么问题的,速度也很快。

好了,使用Ollama在本地部署llama3的教程就为大家教到这里,大家快动手试试,部署个属于自己的本地大模型助手吧!

✈️ 诸位好,我是斜杠君。全栈技术,AI工作流技术研究者。分享探索AI技术等干货内容。如果您有关于扣子CozeAI工作流的特别需求问题,也可以通过 爱发电 向我提问。

爱发电 · 连接创作者与粉丝的会员制平台

阁下若觉此文有益,恳请 -点赞 ⭐ - 收藏 - 关注,以资鼓励。倘若有疑问或建言,亦请在评论区 评论 赐教,吾将感激不尽。请关注我的公众号:斜杠君。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Flutter 多项目版本管理 如何优雅地处理不同 Flut

发表于 2024-04-25

同名公众号:小武码码码

在过往项目中,当作为一名 Flutter 开发者时,我在日常开发中经常需要同时管理和维护多个 Flutter 项目。这些项目可能使用不同的 Flutter SDK 版本,导致在切换项目时遇到versioning的问题。我曾经在开发一个电商项目时遇到了这样的困扰:该项目使用的是 Flutter 2.5.0 版本,而我同时还在开发另一个使用 Flutter 3.0.0 版本的项目。在两个项目之间切换时,我需要手动修改环境变量和 IDE 设置,这非常繁琐和容易出错。

为了解决这个问题,我探索了几种方法来管理多个 Flutter 项目的不同 SDK 版本,下面我将详细分享这些方法。

方法一:使用 Flutter Version Management (FVM)

FVM 是一个非常有用的工具,它允许你在系统上安装和管理多个 Flutter SDK 版本。通过 FVM,你可以为每个项目指定特定的 Flutter SDK 版本,而不会影响其他项目。

  1. 安装 FVM:
1
csharp复制代码dart pub global activate fvm


2. 在项目根目录下创建 .fvm/fvm_config.json 文件,指定所需的 Flutter SDK 版本:

1
2
3
json复制代码{
"flutterSdkVersion": "2.5.0"
}


3. 在项目根目录下运行以下命令,安装和使用指定版本的 Flutter SDK:

1
2
perl复制代码fvm install
fvm use


4. FVM 会自动切换到项目指定的 Flutter SDK 版本。

假设我有三个项目:项目 A、项目 B 和项目 C,它们分别需要使用 Flutter 2.5.0、3.0.0 和 2.8.1 版本。我可以在每个项目的根目录下创建相应的 fvm_config.json 文件,指定所需的 Flutter SDK 版本。然后,在切换项目时,只需运行 fvm install 和 fvm use 命令,FVM 就会自动为每个项目安装和切换到相应的 Flutter SDK 版本。

方法二:手动切换 Flutter SDK 版本

如果你没有使用 FVM,也可以手动切换 Flutter SDK 版本。

  1. 下载并安装所需版本的 Flutter SDK。
  2. 更新系统环境变量中的 PATH,将新版本的 Flutter SDK 路径放在旧版本之前。

例如,我将不同版本的 Flutter SDK 安装在以下路径:

  • Flutter SDK 2.5.0: /path/to/flutter_2.5.0
  • Flutter SDK 3.0.0: /path/to/flutter_3.0.0
  • Flutter SDK 2.8.1: /path/to/flutter_2.8.1

当我切换到项目 A 时,需要将 Flutter SDK 2.5.0 的路径添加到 PATH 的最前面:

1
bash复制代码export PATH="/path/to/flutter_2.5.0/bin:$PATH"

类似地,切换到项目 B 和项目 C 时,需要相应地更新 PATH 变量。

方法三:使用不同的 IDE 配置

如果你使用 Android Studio 或 VS Code 等 IDE,可以为不同的项目配置不同的 Flutter SDK 路径。

以 Android Studio 为例:

  1. 打开项目 A,进入 “File” -> “Settings” -> “Languages & Frameworks” -> “Flutter”,然后指定 Flutter SDK 2.5.0 的路径。
  2. 打开项目 B,进入 “Settings”,指定 Flutter SDK 3.0.0 的路径。
  3. 打开项目 C,进入 “Settings”,指定 Flutter SDK 2.8.1 的路径。

对于 VS Code,可以在每个项目的根目录下创建或修改 .vscode/settings.json 文件,添加以下配置:

1
2
3
json复制代码{
"dart.flutterSdkPath": "/path/to/flutter_sdk"
}

将 /path/to/flutter_sdk 替换为相应项目的 Flutter SDK 路径即可。

方法四:使用不同的终端窗口或虚拟环境

如果你在命令行中工作,可以为每个项目打开单独的终端窗口,并在每个窗口中设置相应的 Flutter SDK 路径。

例如,对于项目 A,打开一个新的终端窗口,切换到项目 A 的根目录,然后运行:

1
bash复制代码export PATH="/path/to/flutter_2.5.0/bin:$PATH"

对于项目 B 和项目 C,可以打开单独的终端窗口,并设置相应的 Flutter SDK 路径。

另一种方法是使用虚拟环境,为每个项目创建独立的环境,并在其中安装特定版本的 Flutter SDK。可以使用 virtualenv 或 Docker 等工具来创建和管理虚拟环境。

总结

管理多个 Flutter 项目的不同 SDK 版本可能会带来一些挑战,但是通过使用 FVM、手动切换 Flutter SDK 版本、配置 IDE 设置或使用不同的终端窗口或虚拟环境,我们可以有效地解决这个问题。

在我的电商项目开发过程中,使用 FVM 管理 Flutter SDK 版本带来了很大的便利。我可以轻松地在不同项目之间切换,而不用担心版本冲突或环境设置的问题。这大大提高了我的开发效率,同时也减少了出错的可能性。

我建议所有从事 Flutter 开发的同学都尝试使用这些方法来管理多个项目的 Flutter SDK 版本。选择最适合自己工作流程和开发环境的方法,就可以更加优雅地处理 Flutter 项目的版本管理问题,提升开发体验和效率。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Apache JMeter进行TCP并发压力测试初尝试 前言

发表于 2024-04-25

前言

由于互联网编程实验二第三题要求比较使用线程池与否的服务器的并发性能,遂检索信息并了解到Apache JMeter这个工具

本文主要介绍了在已有Java JDK的情况下对Apache JMeter的安装及配置,以及利用JMeter进行TCP压力测试

一、安装及配置

先在官网下载压缩包:Apache JMeter - Download Apache JMeter

image.png

将文件apache-jmeter-5.6.3.zip解压到自己选择的目录中

在根目录下,找到bin文件夹,进入文件夹中,找到jmeter.bat,双击即可打开软件

二、TCP服务器

编写Java代码,在Server类的main函数中,指定一个端口作为服务器端口,并在while死循环中不断接收客户端的请求,对于每一个请求新开一个线程ThreadServer,并在其中处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class Server {  
static int PORT = 9000;
static int MAX_POOL = 100;
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(PORT);

// without thread pool

while (true) {
Socket accept = server.accept();
Thread thread = new Thread(new ThreadServer(accept));
thread.start();
}

// with thread pool

// ExecutorService service = Executors.newFixedThreadPool(MAX_POOL);
// while (true) {
// Socket accept = server.accept();
// service.submit(new ThreadServer(accept));
// }
}
}

代码中将服务器分为两种模式,第一段为没有线程池的模式,第二段为使用线程池的模式。通过分别注释并重新编译运行来启动不同模式的服务器

为了简化服务端和客户端的交互,我们在ThreadServer中仅仅打印连接成功以及断开连接的信息,而不进行多余的通信,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
java复制代码public class ThreadServer implements Runnable{  
Socket socket;
static int BUFFER_SIZE = 1024;
static String EXIT_STR = "exit";
public ThreadServer(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try{
System.out.println("connection start");
OutputStream out = socket.getOutputStream();
out.write("connect successfully".getBytes());
// echo service
// InputStream in = socket.getInputStream();
// byte[] inBytes = new byte[BUFFER_SIZE];
// int len;
// while((len = in.read(inBytes)) != 0){
// String str = new String(inBytes, 0, len);
// if(str.equals(EXIT_STR))
// break;
// System.out.println("received: " + str);
// out.write(str.getBytes());
// }
System.out.println("connection end");
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}

注释部分是提供echo服务,需要客户端同步实现,在本题中无需使用,客户端参考以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码public class Client {  
static String SERVER_HOST = "127.0.0.1";
static int SERVER_PORT = 9000;
static int BUFFER_SIZE = 1024;
static String EXIT_STR = "exit";
public static void main(String[] args) throws IOException {
Socket client = new Socket(SERVER_HOST, SERVER_PORT);
Scanner scanner = new Scanner(System.in);
OutputStream out = client.getOutputStream();
InputStream in = client.getInputStream();
byte[] buffer = new byte[BUFFER_SIZE];

// echo
while(scanner.hasNextLine()){
String lineOfWord = scanner.nextLine();
out.write(lineOfWord.getBytes());
if(lineOfWord.equals(EXIT_STR))
break;
int len = in.read(buffer);
System.out.println("echo: " + new String(buffer, 0, len));
}
client.close();
}
}

三、并发测试

  1. 创建测试计划

打开JMeter,右键单击测试计划(test plan),一直选择到新建线程组,如下

image.png

右键单击新建的线程组,分别新建TCP Sampler和Response Time Graph,分别用于TCP连接以及输出响应时间与时间的折线图

image.png

image.png

注意到TCP Sampler属于Sampler模块,我们也可在此模块中选择HTTP Request进行HTTP请求的测试;Response Time Graph属于Listener模块,我们可以在此模块中选择Aggregate Report输出测试的聚合报告,包括响应时间的平均值、最小值,以及吞吐量等

在TCP Sampler中,指定服务器的地址及端口号如下

image.png

在Response Time Graph指定记录的间隔,单位为ms,这里设置为1000

image.png

  1. 正式测试

首先启动服务端

1
2
3
cmd复制代码D:\idea project\internetprog\exp2\exp2\src>javac Server.java

D:\idea project\internetprog\exp2\exp2\src>java Server

单击Thread Group,不断更改以下几个参数,测试不同程度的并发下服务的响应时间

image.png

其中

  1. Number of Threads即创建的线程数
  2. Ramp-up period即在多长时间内创建以上线程数,单位为s
  3. Loop Count即执行的次数,勾选Infinite代表无限次执行

在本例中,我选择固定Ramp-up period为1,Loop Count为5,修改Number of Threads依次为2000、4000、6000、8000以及10000,分别对比在有无线程池的服务器中,出现较长延迟的响应时间。每种情况分别测试3~5次。

测试结果如下

线程数 无线程池 有线程池
2000 1ms内 1ms内
4000 1s内 1s内
6000 2s内 2s内
8000 4s ~ 8s 3s ~ 6s
10000 10s ~ 16s 3s ~ 7s

可以看出,当并发压力增大时,无线程池出现的长延迟响应时间,相比有线程池的情况增长要快

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

黄仁勋亲自给OpenAI送货,全球首台DGX H200开箱了

发表于 2024-04-25

又是一张历史性大合照。

今天,黄仁勋又来送 AI 芯片了,还是超强悍的那种。

OpenAI 联合创始人、总裁 Greg Brockman 发推,晒出了自己、OpenAI CEO 奥特曼与英伟达创始人兼 CEO 黄仁勋的合照。

他表示,老黄亲自为 OpenAI 送来了全球第一台 Nvidia DGX H200 超级计算机,此举意在双方共同推进 AI、计算和人类的发展。

图片

不过,网友更关注的依然是 OpenAI 联合创始人、首席科学家 Ilya Sutskever。自去年 12 月 15 日转推了 OpenAI 的一个帖子以来,他已经在 X 上「消失」了四个多月。

图片

有网友调侃到,Ilya 是不是在边上的箱子里呢?

图片

还有人 P 了一张 Ilya 与奥特曼搭肩的合照。

图片

把最强 AI 算力赠送给业界先进研究机构,这件事英伟达已不是第一次做了。

今年 2 月,马斯克还在 X 上回忆了老黄向 OpenAI 捐赠 DGX-1 服务器的那张经典照片。作为 OpenAI 的联合创始人,彼时马斯克还没有因为「利益冲突」和开源等问题与 OpenAI 反目成仇。

图片

2016 年,黄仁勋亲自向 OpenAI 赠送了全球第一台 DGX-1,马斯克也在接收现场。

或许就是这个举动,催生了 ChatGPT 的到来。

DGX-1 被英伟达称作为「AI 超级计算机」,当时捐赠的版本价值 12.9 万美元,集成了 8 块 Tesla P100(Pascal 架构)GPU,整个系统的机器学习算力为 170 teraflops(FP16)。

在此之后,英伟达陆续推出了 DGX-2(Volta 架构)、DGX A100 服务器(Ampere 架构),持续引领了 AI 算力的最高水平。

今天赠送给 OpenAI 的 DGX H200,是去年 11 月在全球超算大会上推出的最新产品。英伟达在官方博客中曾表示:H200 Tensor Core GPU 具有改变游戏规则的性能和内存功能,可增强生成式 AI 和高性能计算 (HPC) 工作负载。单块 H200 的 FP16 算力是 1979 TFLOPS。

作为首款采用 HBM3e 的 GPU,H200 借助更大更快的内存可加速生成式 AI 和大型语言模型 (LLM) 的运行,同时推进 HPC 工作负载的科学计算。

图片

老黄送显卡,看来正在成为英伟达的惯例。不过还有一个不得不说的故事,就是神经网络刚刚兴起的时候,英伟达曾经「拒绝」过人工智能先驱 Geoffrey Hinton。

2009 年 Hinton 的研究小组在使用英伟达的 CUDA 平台训练神经网络来识别人类语音。他对研究结果的质量感到惊讶,并于当年晚些时候的一次会议上介绍了这些结果。Hinton 联系了英伟达,「我发了一封电子邮件说,听着,我刚刚告诉一千名机器学习研究人员,他们应该去购买英伟达 GPU。你能给我免费寄来一份吗?」结果英伟达拒绝了。

尽管受到冷落,Hinton 仍然鼓励他的学生使用 CUDA,其中包括传奇学者 Alex Krizhevsky。2012 年,Krizhevsky 和他的研究伙伴 Ilya Sutskever 在预算紧张的情况下从亚马逊购买了两张 GeForce 显卡,训练视觉识别神经网络,这才有了一代经典 AlexNet。

不过在 2018 年 9 月,在一次庆祝英伟达多伦多 AI 实验室(Toronto AI Lab)成立的会上,黄仁勋送给了 Hinton 一块 TITAN V 显卡。这也算是弥补了当初拒绝 Hinton 的遗憾。

图片

图源:twitter.com/PengDai\_c…

除了交付 AI 算力,英伟达在投资领域也有新的动向。本周三据彭博社等媒体报道,英伟达正式宣布有意收购 AI 基础设施管理平台 Run:ai。该交易的价值并未透露,但估计约为 7 亿美元。

Run:ai 由 Omri Geller(首席执行官)和 Ronen Dar (首席技术官)于 2018 年创立。该公司开发了一个并行算力的编排和虚拟化软件层,以满足在 GPU 和类似芯片组上运行的人工智能工作负载的独特需求。Run:ai 基于 Kubernetes 的 AI 云容器平台通过自动分配必要的计算能力(从部分 GPU 到多个 GPU,再到多个 GPU 节点),能够帮助用户有效地池化和共享 GPU。

不断扩充朋友圈,同时资助构建 GPU 算力的公司,英伟达的 AI 版图已越来越大。

参考内容:

twitter.com/gdb/status/…

www.bloomberg.com/news/articl…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

管道数据过读导致堵塞问题分析 背景 原问题 化简为繁 特征复

发表于 2024-04-25

背景

在我们的内部 Monkey 测试中频繁发生以下特征的 watchdog、anr 等现象,某个进程所有线程都被 ptrace_stop 以及其中一线程状态处于 pipe_read。

1
2
3
ini复制代码sysTid=xx1 ptrace_stop
sysTid=xx2 ptrace_stop
sysTid=xx3 pipe_read

原问题

system_server 进程与 vold 之间的 binder 通讯上阻塞了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
less复制代码"main" prio=5 tid=1 Native
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x722098b8 self=0xb400006f29242c00
| sysTid=1584 nice=-2 cgrp=foreground sched=0/0 handle=0x6fdbaa84f8
| state=S schedstat=( 368940832460 343970545986 1269658 ) utm=22380 stm=14513 core=4 HZ=100
| stack=0x7ff65eb000-0x7ff65ed000 stackSize=8188KB
| held mutexes=
native: #00 pc 000eed78 /apex/com.android.runtime/lib64/bionic/libc.so (__ioctl+8) (BuildId: 5bf69b75cc8574e27203cdbd408d1e67)
native: #01 pc 000a0e0c /apex/com.android.runtime/lib64/bionic/libc.so (ioctl+156) (BuildId: 5bf69b75cc8574e27203cdbd408d1e67)
native: #02 pc 000614d8 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver+280) (BuildId: 0bccce6a77300aaeb158148372160959)
native: #03 pc 0006289c /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse+60) (BuildId: 0bccce6a77300aaeb158148372160959)
native: #04 pc 000625d8 /system/lib64/libbinder.so (android::IPCThreadState::transact+216) (BuildId: 0bccce6a77300aaeb158148372160959)
native: #05 pc 0007ae78 /system/lib64/libbinder.so (android::BpBinder::transact+232) (BuildId: 0bccce6a77300aaeb158148372160959)
native: #06 pc 001a2118 /system/lib64/libandroid_runtime.so (android_os_BinderProxy_transact+152) (BuildId: ee14d1b8806d6c50e82ba389f69ce923)
at android.os.BinderProxy.transactNative(Native method)
at android.os.BinderProxy.transact(BinderProxy.java:621)
at android.os.IVold$Stub$Proxy.abortIdleMaint(IVold.java:2022)
at com.android.server.StorageManagerService.abortIdleMaint(StorageManagerService.java:2845)
at com.android.server.StorageManagerService$2.onReceive(StorageManagerService.java:906)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1866)
at android.app.LoadedApk$ReceiverDispatcher$Args.$r8$lambda$gDuJqgxY6Zb-ifyeubKeivTLAwk(unavailable:0)
at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(unavailable:2)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:222)
at android.os.Looper.loop(Looper.java:314)
at com.android.server.SystemServer.run(SystemServer.java:1057)
at com.android.server.SystemServer.main(SystemServer.java:725)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:565)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1059)

然而 vold 进程的状态却被其它进程 ptrace_stop 挂起,无法查看它堆栈。

1
2
3
4
5
6
7
8
9
ini复制代码----- Waiting Channels: pid 524 at 2023-08-30 04:30:18.586759099+0800 -----
Cmd line: /system/bin/vold --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
sysTid=524 ptrace_stop
sysTid=531 ptrace_stop
sysTid=532 ptrace_stop
sysTid=533 ptrace_stop
sysTid=619 ptrace_stop
sysTid=857 ptrace_stop
sysTid=7327 pipe_read

而 crash_dump64 进程,父进程等管道数据,子进程等某进程结束。

1
2
yaml复制代码u:r:crash_dump:s0 root 7328 7328 524 2241480 2996 pipe_read 0 S 19 0 - 0 fg 5 crash_dump64 crash_dump64
u:r:crash_dump:s0 root 7329 7329 7328 2241608 1252 do_wait 0 S 19 0 - 0 fg 5 crash_dump64 crash_dump64

诸如此类问题,没啥好的办法,依据特征保存机器现场。

化简为繁

对 debuggerd_client.cpp 的 get_wchan_data 添加特征检测触发内核 panic 来得到该特征现场的 Ramdump 文件,然后在恢复用户态进程数据,详情参考《浅谈安卓离线内存分析项目》。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
c复制代码static std::string get_wchan_data(int fd, pid_t pid) {
std::vector<pid_t> tids;
if (!android::procinfo::GetProcessTids(pid, &tids)) {
log_error(fd, 0, "failed to get process tids");
return "";
}

std::stringstream data;
for (int tid : tids) {
std::string path = "/proc/" + std::to_string(pid) + "/task/" + std::to_string(tid) + "/wchan";
std::string wchan_str;
if (!ReadFileToString(path, &wchan_str, true)) {
log_error(fd, errno, "failed to read \"%s\"", path.c_str());
continue;
}
}

std::stringstream buffer;
if (std::string str = data.str(); !str.empty()) {
+ std::string cmdline = android::base::Join(get_command_line(pid), " ");
+ if (cmdline == "/system/bin/lmkd"
+ || cmdline == "/system/bin/netd"
+ || cmdline == "/system/bin/vold"
+ || cmdline == "/vendor/bin/hw/android.hardware.health-service.example") {
+ std::size_t index = str.find("ptrace_stop");
+ if (index != std::string::npos) {
+ WriteStringToFile("c", "/proc/sysrq-trigger");
+ }
+ }
buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
<< "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
buffer << "\n" << str << "\n";
buffer << "----- end " << std::to_string(pid) << " -----\n";
buffer << "\n";
}
return buffer.str();
}

特征复现

测试一段时间后,终于得到一例发生 echo c > /proc/sysrq-trigger 触发内核死机问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
css复制代码[<ffffffc008f8d22c>] panic+0x190/0x388
[<ffffffc0087f4ef8>] sysrq_handle_crash+0x20/0x24
[<ffffffc0087f4a78>] __handle_sysrq+0xe8/0x1b0
[<ffffffc0087f5ca0>] write_sysrq_trigger+0xf4/0x178
[<ffffffc00842f5f4>] proc_reg_write+0xf0/0x168
[<ffffffc008382ecc>] vfs_write+0x104/0x2c8
[<ffffffc008383218>] ksys_write+0x74/0xe8
[<ffffffc0083832a4>] __arm64_sys_write+0x18/0x2c
[<ffffffc00802efe0>] invoke_syscall+0x54/0x11c
[<ffffffc00802eef4>] el0_svc_common+0x84/0xf4
[<ffffffc00802ede8>] do_el0_svc+0x28/0xb0
[<ffffffc008f94530>] el0_svc+0x28/0xa4
[<ffffffc008f944b8>] el0t_64_sync_handler+0x64/0xb4
[<ffffffc00801157c>] el0t_64_sync+0x19c/0x1a4

首先我们得确定本次 panic 是我们添加调试代码触发的,那么我们可以先将 panic 进程转储一份 Core 文件出来进行栈回溯。crash 转储 Core 可参考《Crash 插件开发指南》。开源版本 lp 插件比 ram2core 性能以及其它均更优,大家可尝试用这个替代公版 crash-gcore。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shell复制代码crash> ps -G 27252
PID PPID CPU TASK ST %MEM VSZ RSS COMM
1723 942 6 ffffff8118d8cb00 IN 1.8 15121824 166036 system_server

crash> ram2core -p 1723 -s zram -m shmem
Write ELF Header
Write Program Headers
Write Segments
>>>> 10% <<<<
>>>> 20% <<<<
>>>> 30% <<<<
>>>> 40% <<<<
>>>> 50% <<<<
>>>> 60% <<<<
>>>> 70% <<<<
>>>> 80% <<<<
>>>> 90% <<<<
Done
Saved [core.1723].

并且对 system_server 的 Core 文件 Java 部分进行修复后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
less复制代码art-parser> bt 27252
"AnrConsumer" prio=10 tid=313 Native
| group="main" sCount=0 ucsCount=0 flags=0 obj=0x14641848 self=0xb400007c00374c00
| sysTid=27252 nice=<unknown> cgrp=<unknown> sched=<unknown> handle=0x7b28d7acb0
| stack=0x7b28c77000-0x7b28c79000 stackSize=0x103cb0
| held mutexes=
x0 0x00000000000004f5 x1 0x0000007b28d7a251 x2 0x0000000000000001 x3 0xffffffffffffffff
x4 0xffffffffffffffff x5 0x0000007b28d7a24c x6 0x0000000000000010 x7 0x7f7f7f7f7f7f7f7f
x8 0x0000000000000040 x9 0x0000000000000000 x10 0x0000000000000001 x11 0x0000007b28d7a080
x12 0xffffff80ffffffd0 x13 0x0000007b28d7a0b0 x14 0x0000000000000000 x15 0x0000000000000100
x16 0x0000007db6f3aae0 x17 0x0000007db6f14a20 x18 0x0000007ae673a000 x19 0x0000007b28d7a238
x20 0x0000007b28d7a251 x21 0x00000000000004f5 x22 0x0000000000000001 x23 0x0000007b28d7a221
x24 0x0000007b28d7b000 x25 0x0000007b28d7a239 x26 0x0000007b28d7a2d8 x27 0x0000007b28d7a2d0
x28 0x0000007b28d7a3f8 x29 0x0000007b28d7a140
lr 0x0000007db7cd09f4 sp 0x0000007b28d7a130 pc 0x0000007db6f14a28 pst 0x0000000000001000
FP[0x7b28d7a140] PC[0x7db6f14a28] native: #00 (write+0x8) /apex/com.android.runtime/lib64/bionic/libc.so
FP[0x7b28d7a140] PC[0x7db7cd09f4] native: #01 (android::base::WriteStringToFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool)+0xe4) /system/lib64/libbinder.so
FP[0x7b28d7a510] PC[0x7dd19907f4] native: #02 (dump_backtrace_to_file_timeout(int, DebuggerdDumpType, int, int)+0x698) /system/lib64/libdebuggerd_client.so
FP[0x7b28d7a590] PC[0x7dcd0651e0] native: #03 (std::__1::__tree<std::__1::__value_type<std::__1::basic_string_view<char, std::__1::char_traits<char> >, unsigned long>, std::__1::__map_value_compare<std::__1::b
asic_string_view<char, std::__1::char_traits<char> >, std::__1::__value_type<std::__1::basic_string_view<char, std::__1::char_traits<char> >, unsigned long>, std::__1::less<std::__1::basic_string_view<char, std::__1::char_traits<char> > >, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string_view<char, std::__1::char_traits<char> >, unsigned long> > >::destroy(std::__1::__tree_node<std::__1::__value_type<std::__1::basic_string_view<char, std::__1::char_traits<char> >, unsigned long>, void*>*)+0x4a0) /system/lib64/libandroid_runtime.so
FP[0x7b28d7a5e0] PC[0x7dcd062b6c] native: #04 (android::register_android_os_Debug(_JNIEnv*)+0x176c) /system/lib64/libandroid_runtime.so
QF[0x7b28d7a5f0] PC[0x0000000000] at dex-pc 0x0000000000 android.os.Debug.dumpNativeBacktraceToFileTimeout(Native method) //AM[0x7102bd60]
QF[0x7b28d7a6a0] PC[0x009d1c7854] at dex-pc 0x7d1a34bd66 com.android.server.am.StackTracesDumpHelper.dumpStackTraces //AM[0x7c964bb5a0]
QF[0x7b28d7a750] PC[0x009d5bf91c] at dex-pc 0x7d1a34b3a2 com.android.server.am.StackTracesDumpHelper.dumpStackTraces //AM[0x7c964bb600]
QF[0x7b28d7a7e0] PC[0x009d3ec00c] at dex-pc 0x7d1a3306d0 com.android.server.am.ProcessErrorStateRecord.appNotResponding //AM[0x9b018b90]
QF[0x7b28d7a9c0] PC[0x009d4c8ab0] at dex-pc 0x7d1a2cd1bc com.android.server.am.AnrHelper$AnrRecord.appNotResponding //AM[0x7c964bb7b0]
QF[0x7b28d7aa50] PC[0x009d42045c] at dex-pc 0x7d1a2cd01c com.android.server.am.AnrHelper$AnrConsumerThread.run //AM[0x9b012068]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ini复制代码art-parser> bt 27252 -v
QF[0x7b28d7aa50] PC[0x009d42045c] at dex-pc 0x7d1a2cd01c com.android.server.am.AnrHelper$AnrConsumerThread.run //AM[0x9b012068]
{
StackMap[31] (code_region=[0x9d4201a0-0x9d42046c], native_pc=0x2bc, dex_pc=0x4c, register_mask=0x1800000)
Virtual registers
{
v0 = r0 v1 = r24 v2 = r26 v3 = r25
v4 = r28 v13 = r23
}
Physical registers
{
x22 = 0x9b012068 x23 = 0x14641848 x24 = 0x1a631e38 x25 = 0x152c
x26 = 0x823de91 x27 = 0x0 x28 = 0x7093eca0 x29 = 0x1a6fc2f0
x30 = 0x9d42045c
}
}
1
2
3
4
php复制代码art-parser> disassemble 0x9b012068 -i 0x7d1a2cd01c
void com.android.server.am.AnrHelper$AnrConsumerThread.run() [dex_method_idx=29861]
DEX CODE:
0x7d1a2cd01c: 206e 74a8 0021 | invoke-virtual {v1, v2}, void com.android.server.am.AnrHelper$AnrRecord.appNotResponding(boolean) // method@29864
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ini复制代码art-parser> p 0x1a631e38
Size: 0x38
Padding: 0x2
Object Name: com.android.server.am.AnrHelper$AnrRecord
iFields of com.android.server.am.AnrHelper$AnrRecord
[0x34] boolean mAboveSystem = 0x0
[0x8] java.lang.String mActivityShortComponentName = com.android.browser/.BrowserActivity
[0xc] com.android.server.am.ProcessRecord mApp = 0x1464a438
[0x10] android.content.pm.ApplicationInfo mAppInfo = 0x1a61a9b8
[0x14] java.util.concurrent.Future mFirstPidFilePromise = 0x1bf21ab8
[0x35] boolean mIsContinuousAnr = 0x1
[0x18] com.android.server.wm.WindowProcessController mParentProcess = 0x1464f868
[0x1c] java.lang.String mParentShortComponentName = com.android.browser/.BrowserActivity
[0x30] int mPid = 0x6b05
[0x20] com.android.internal.os.TimeoutRecord mTimeoutRecord = 0x1bf21ad8
[0x28] long mTimestamp = 0x823c965
[0x24] com.android.server.am.AnrHelper this$0 = 0x132d3e18
iFields of java.lang.Object
[0x0] java.lang.Class shadow$_klass_ = 0x16bbb6a0
[0x4] int shadow$_monitor_ = 0x0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
less复制代码(gdb) bt
#0 write () at out_sys/soong/.intermediates/bionic/libc/syscalls-arm64/gen/syscalls-arm64.S:500
#1 0x0000007db7cd09f4 in write(int, void const*, unsigned long pass_object_size0) [clone .__uniq.26443029927838627478261023667953154577] (fd=1269, buf=0x7b28d7a251, count=1)
at bionic/libc/include/bits/fortify/unistd.h:174
#2 android::base::WriteStringToFd (content=..., fd=...) at system/libbase/file.cpp:252
#3 android::base::WriteStringToFile (content=..., path=..., follow_symlinks=<optimized out>) at system/libbase/file.cpp:308
#4 0x0000007dd19907f4 in get_wchan_data (fd=801, pid=453) at system/core/debuggerd/client/debuggerd_client.cpp:127
#5 dump_backtrace_to_file_timeout (tid=tid@entry=453, dump_type=<optimized out>, dump_type@entry=kDebuggerdNativeBacktrace, timeout_secs=timeout_secs@entry=2, fd=801)
at system/core/debuggerd/client/debuggerd_client.cpp:340
#6 0x0000007dcd0651e0 in android::dumpTraces (env=0xb400007d0afc8500, pid=453, fileName=0x7b28d7a6ac, timeoutSecs=2, dumpType=kDebuggerdNativeBacktrace, dumpType@entry=224)
at frameworks/base/core/jni/android_os_Debug.cpp:830
#7 0x0000007dcd062b6c in android::android_os_Debug_dumpNativeBacktraceToFileTimeout (env=0x4f5, clazz=<optimized out>, pid=685220433, fileName=0x1, timeoutSecs=-1)
at frameworks/base/core/jni/android_os_Debug.cpp:845
#8 0x0000000071db8a6c in android::os::Debug::dumpJavaBacktraceToFileTimeout ()

还原数据可知 system_server 正在 dump com.android.browser/.BrowserActivity 的 anr_trace.txt 过程中,并且 get_wchan_data 此时捕捉到特征发生在进程 453 身上。回到 crash 环境下,查看进程 453 的所有线程都在处于 ptrace_stop,以及一个线程处于 pipe_read 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
yaml复制代码crash> bt -g 453
PID: 453 TASK: ffffff800f54cb00 CPU: 2 COMMAND: "lmkd"
PID: 460 TASK: ffffff801a4db840 CPU: 4 COMMAND: "lmkd_reaper0"
PID: 461 TASK: ffffff801a4dcb00 CPU: 5 COMMAND: "lmkd_reaper1"
PID: 462 TASK: ffffff801a4d8000 CPU: 5 COMMAND: "lmkd_watchdog"
#3 [ffffffc00e9fbc90] ptrace_stop at ffffffc0080ce5fc

PID: 27712 TASK: ffffff8174715dc0 CPU: 3 COMMAND: "lmkd"
#0 [ffffffc03e163b70] __switch_to at ffffffc008f9b62c
#1 [ffffffc03e163bc0] __schedule at ffffffc008f9be14
#2 [ffffffc03e163c20] schedule at ffffffc008f9c270
#3 [ffffffc03e163cc0] pipe_read at ffffffc0083910dc
#4 [ffffffc03e163d80] vfs_read at ffffffc008382758
#5 [ffffffc03e163dd0] ksys_read at ffffffc008383104
#6 [ffffffc03e163e10] __arm64_sys_read at ffffffc008383190
#7 [ffffffc03e163e20] invoke_syscall at ffffffc00802efe0
#8 [ffffffc03e163e40] el0_svc_common at ffffffc00802ef20
#9 [ffffffc03e163e70] do_el0_svc at ffffffc00802ede8
#10 [ffffffc03e163e80] el0_svc at ffffffc008f94530
#11 [ffffffc03e163ea0] el0t_64_sync_handler at ffffffc008f944b8
#12 [ffffffc03e163fe0] el0t_64_sync at ffffffc00801157c
PC: 0000007d1755f758 LR: 0000007d174947b0 SP: 0000007d16361ed0
X29: 0000007d16361f60 X28: 0000000000000000 X27: 0000007d1628a000
X26: 0000000000000000 X25: 0000000000006c41 X24: 0000000000000005
X23: 0000000000000004 X22: 0000000000000006 X21: 0000000000000003
X20: 0000007d17424bf0 X19: 0000007d175847f8 X18: 0000007d1652a000
X17: 0000007d17546280 X16: 0000007d17578e98 X15: 0000019d56bbc12f
X14: 00000000040fda32 X13: 0000000000004100 X12: 00000000ffffffff
X11: 0000000000000000 X10: 0000000000000000 X9: dbe14e7e76532670
X8: 000000000000003f X7: 7f7f7f7f7f7f7f7f X6: 0000007d17424bf0
X5: 0000007d17424bf0 X4: 0000007d17424bf0 X3: 0000007d17424bf0
X2: 0000000000000004 X1: 0000007d16361ee8 X0: 0000000000000003
ORIG_X0: 0000000000000003 SYSCALLNO: 3f PSTATE: 60001000

离线分析

从堆栈以及寄存器上下文,可以知道线程 27712 正在读取管道 FD(0x3),接下来将进程 lmkd 453 转储一份 Core 文件,以便我们分析父子进程的关系。

1
2
3
4
5
6
less复制代码// 进程 453 堆栈
(gdb) bt
#0 __dl_read () at out_sys/soong/.intermediates/bionic/libc/syscalls-arm64/gen/syscalls-arm64.S:488
#1 0x0000007d174947b0 in read(int, void*, unsigned long pass_object_size0) (fd=-1, buf=0x7d16361ee8, count=4) at bionic/libc/include/bits/fortify/unistd.h:162
#2 debuggerd_dispatch_pseudothread (arg=arg@entry=0x7d17424bf0) at system/core/debuggerd/handler/debuggerd_handler.cpp:486
#3 0x0000007d17548a14 in __start_thread (fn=0x7d174942e4 <debuggerd_dispatch_pseudothread(void*)>, arg=0x7d17424bf0) at bionic/libc/bionic/clone.cpp:53
1
2
3
4
5
6
7
8
9
less复制代码(gdb) frame 2
#2 debuggerd_dispatch_pseudothread (arg=arg@entry=0x7d17424bf0) at system/core/debuggerd/handler/debuggerd_handler.cpp:486

(gdb) info locals
input_read = {
fd_ = 3
}

crash_dump_pid = 27713

Screenshot from 2024-04-25 11-52-54.png

这里我们可以知道 lmkd 线程 27712 在 debuggerd_handler.cpp:486(对应 Google 原生代码如图)处等待,而这个消息需要子进程 crash_dump 发送过来,因此我们在转储进程 27713 Core 文件。

1
2
3
4
5
shell复制代码// 进程 27713 堆栈
(gdb) bt
#0 read () at out_sys/soong/.intermediates/bionic/libc/syscalls-arm64/gen/syscalls-arm64.S:488
#1 0x0000005a9ada9840 in read(int, void*, unsigned long pass_object_size0) (fd=7, buf=0x7fd9770e60, count=1) at bionic/libc/include/bits/fortify/unistd.h:162
#2 main (argc=<optimized out>, argv=<optimized out>) at system/core/debuggerd/crash_dump.cpp:453

Screenshot from 2024-04-25 11-56-12.png
同样的 crash_dump (27713) 在读取管道 FD(0x7),获得一个字符后退出,我们需进行分析子进程 forkpid 的场景。

1
2
3
yaml复制代码crash> ps | grep 27713
27713 27712 1 ffffff80c07e2580 IN 0.0 2223184 3776 crash_dump64
27732 27713 3 ffffff811881cb00 IN 0.0 2223312 1500 crash_dump64

找到子进程 27732 后,进一步转储 Core 下来到 gdb 上解析。

1
2
3
4
5
6
7
shell复制代码(gdb) thread 
[Current thread is 1 (LWP 27732)]
(gdb) bt
#0 wait4 () at out_sys/soong/.intermediates/bionic/libc/syscalls-arm64/gen/syscalls-arm64.S:2570
#1 0x0000005a9adaa370 in wait_for_clone (pid=27712, resume_child=true) at system/core/debuggerd/crash_dump.cpp:384
#2 0x0000005a9ada8058 in wait_for_vm_process (pseudothread_tid=27712) at system/core/debuggerd/crash_dump.cpp:424
#3 main (argc=<optimized out>, argv=<optimized out>) at system/core/debuggerd/crash_dump.cpp:605
1
2
3
4
5
6
7
8
9
10
ini复制代码(gdb) frame 3
#3 main (argc=<optimized out>, argv=<optimized out>) at system/core/debuggerd/crash_dump.cpp:605
605 in system/core/debuggerd/crash_dump.cpp
(gdb) info locals
output_pipe = {
fd_ = 5
}
input_pipe = {
fd_ = 6
}

Screenshot from 2024-04-25 12-00-11.png

从此处我们可以知道 27732 需要等待线程 27712 退出,然而 27712 却阻塞在管道中。

管道阻塞分析

1
2
3
4
5
6
7
8
9
javascript复制代码crash> files 27712
PID: 27712 TASK: ffffff8174715dc0 CPU: 3 COMMAND: "lmkd"
ROOT: /first_stage_ramdisk/ CWD: /first_stage_ramdisk/
FD FILE DENTRY INODE TYPE PATH
0 ffffff81d4b6d400 ffffff8005050dd0 ffffff8003338720 CHR /first_stage_ramdisk/dev/null
1 ffffff81d4b6d400 ffffff8005050dd0 ffffff8003338720 CHR /first_stage_ramdisk/dev/null
2 ffffff81d4b6d400 ffffff8005050dd0 ffffff8003338720 CHR /first_stage_ramdisk/dev/null
3 ffffff81d4b6d7c0 ffffff806fb06410 ffffff80287c4858 FIFO
6 ffffff81d4b6de00 ffffff806fb07790 ffffff80287c74d8 FIFO
1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码crash> files 27713
PID: 27713 TASK: ffffff80c07e2580 CPU: 1 COMMAND: "crash_dump64"
ROOT: /first_stage_ramdisk/ CWD: /first_stage_ramdisk/
FD FILE DENTRY INODE TYPE PATH
0 ffffff81d4b6d900 ffffff806fb07790 ffffff80287c74d8 FIFO
1 ffffff81d4b6c000 ffffff806fb06410 ffffff80287c4858 FIFO
2 ffffff81d4b6d400 ffffff8005050dd0 ffffff8003338720 CHR /first_stage_ramdisk/dev/null
3 ffffff8071286c80 ffffff8006b8e000 ffffff80074a8b20 REG /first_stage_ramdisk/sys/kernel/tracing/trace_marker
4 ffffff8071286140 ffffff810930b110 ffffff8017d5ae38 DIR /first_stage_ramdisk/proc/453
5 ffffff81d4b6c000 ffffff806fb06410 ffffff80287c4858 FIFO
6 ffffff81d4b6d900 ffffff806fb07790 ffffff80287c74d8 FIFO
7 ffffff8027f99b80 ffffff81237071e0 ffffff80287c5378 FIFO
1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码crash> files 27732
PID: 27732 TASK: ffffff811881cb00 CPU: 3 COMMAND: "crash_dump64"
ROOT: /first_stage_ramdisk/ CWD: /first_stage_ramdisk/
FD FILE DENTRY INODE TYPE PATH
0 ffffff81d4b6d900 ffffff806fb07790 ffffff80287c74d8 FIFO
1 ffffff81d4b6c000 ffffff806fb06410 ffffff80287c4858 FIFO
2 ffffff81d4b6d400 ffffff8005050dd0 ffffff8003338720 CHR /first_stage_ramdisk/dev/null
3 ffffff8071286c80 ffffff8006b8e000 ffffff80074a8b20 REG /first_stage_ramdisk/sys/kernel/tracing/trace_marker
4 ffffff8071286140 ffffff810930b110 ffffff8017d5ae38 DIR /first_stage_ramdisk/proc/453
5 ffffff81d4b6c000 ffffff806fb06410 ffffff80287c4858 FIFO
6 ffffff81d4b6d900 ffffff806fb07790 ffffff80287c74d8 FIFO
8 ffffff8027f98280 ffffff81237071e0 ffffff80287c5378 FIFO

UML 图 (12).jpg

从代码上看正常情况下,目前 27732 处于函数 wait_for_vm_process,此前最后一次往管道写入的数据为 ’0x1‘ ,并且进程 27712 不应该会发生管道阻塞,而会正常退出的。

Screenshot from 2024-04-25 15-01-37.png

接下来分析管道里的数据状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ini复制代码crash> struct file.private_data ffffff81d4b6c000 -x
private_data = 0xffffff818a2af900,

crash> struct pipe_inode_info.head,tail,bufs 0xffffff818a2af900
head = 1,
tail = 1,
bufs = 0xffffff8016a3ac00,
从 head == tail 可知道,管道里已经没有数据可以读取了。

crash> struct pipe_buffer 0xffffff8016a3ac00 -x
struct pipe_buffer {
page = 0xfffffffe057492c0,
offset = 0x2,
len = 0x0,
ops = 0x0,
flags = 0x10,
private = 0x0
}
offset = 0x2, len = 0x0 说明已经往管道写入过两个字符并且已被读出。

crash> kmem -p 0xfffffffe057492c0
PAGE PHYSICAL MAPPING INDEX CNT FLAGS
fffffffe057492c0 19d24b000 0 0 1 4000000000000000
crash> rd -p 19d24b000
19d24b000: 0000000000000166 f.......

从管道里的数据,可以确定最后一次写入数据 0x1 已经被读过了,并且从数据上我们可以了解到写入 0x1 之前,存在一函数先往管道写入 ‘f’ 字符。
于是找到内部定制的某功能代码实现。大致如下

1
2
3
4
5
c复制代码bool Xxxxx::Xxxxxxx(int input_read_fd) {
char buf[4];
int rc = TEMP_FAILURE_RETRY(read(input_read_fd, &buf, sizeof(buf)));
...
}

流程图.jpg

最后

原因是: 管道双方读写时序不可控,时序上刚好写者进程先发生,往缓存写入两个字符 01 66,然后读者进程,首次读取管道数据 4 个字符,取出了所有数据,导致后面管道阻塞。

1
2
3
4
5
6
c复制代码bool Xxxxx::Xxxxxxx(int input_read_fd) {
- char buf[4];
+ char buf[1];
int rc = TEMP_FAILURE_RETRY(read(input_read_fd, &buf, sizeof(buf)));
...
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Modifier 性能优化

发表于 2024-04-25

在前两篇文章中,我们学习了 Modifier 的基础概念和自定义方法。Modifier 为我们提供了强大的定制能力,但是如果使用不当,也可能带来一些性能问题。合理使用 Modifier 对应的渲染性能至关重要。本文将分享一些 Modifier 优化的实践经验,帮助你更好地控制重组开销,避免不必要的绘制,从而提升应用的流畅度。

减少层级嵌套

在使用 Modifier 时,我们经常会遇到多次嵌套的情况,尤其是在自定义 Modifier 时。然而,过多的嵌套会增加布局开销,导致性能下降。因此,我们需要尽量避免不必要的 Modifier 层级。

例如,下面这段代码:

1
2
3
4
5
6
7
kotlin复制代码Box(
modifier = Modifier
.background(Color.Red)
.padding(16.dp)
.size(100.dp)
.clip(RoundedCornerShape(8.dp))
)

可以优化为:

1
2
3
4
5
6
7
8
9
kotlin复制代码Box(
modifier = Modifier
.size(100.dp)
.background(
color = Color.Red,
shape = RoundedCornerShape(8.dp)
)
.padding(16.dp)
)

在优化后的代码中,我们将background和clip合并为一个 Modifier,从而减少了一层嵌套。这种优化方式不仅可以提高性能,还能让代码更加简洁易读。

避免不必要的重组

另一个常见的性能问题是不必要的重组。当 Composable 函数重新计算时,Compose 会尝试复用之前的实例。但若实例被修改了,Compose 就需要重新创建该实例及其子实例,这个过程成为重组(recomposition)。重组开销可能很大,因此我们应该尽量避免不必要的重组。

例如,下面这段代码:

1
2
3
4
5
6
7
8
9
10
kotlin复制代码@Composable
fun MyComponent(text: String) {
Box(
modifier = Modifier
.background(Color.Red)
.padding(16.dp)
) {
Text(text)
}
}

在每次重组时,Modifier.background(Color.Red).padding(16.dp)都会被重新创建一次。为了避免这种情况,我们可以使用Modifier.composed来记住 Modifier 实例:

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码@Composable
fun MyComponent(text: String) {
val modifier = remember {
Modifier
.background(Color.Red)
.padding(16.dp)
}
Box(modifier = modifier) {
Text(text)
}
}

通过remember函数,我们可以确保 Modifier 实例只会在初始化时创建一次,后续的重组就可以直接复用该实例,从而避免了不必要的开销。

合理使用 Modifier.lazy

在某些情况下,我们需要根据条件来决定是否应用某个 Modifier。这种情况下,我们可以使用Modifier.lazy来进行条件判断,从而避免不必要的 Modifier 实例创建。

例如,下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kotlin复制代码@Composable
fun MyComponent(showBorder: Boolean) {
Box(
modifier = Modifier
.background(Color.Red)
.padding(16.dp)
.let {
if (showBorder) {
it.border(
width = 2.dp,
color = Color.Black
)
} else {
it
}
}
) {
// ...
}
}

在这个例子中,只有在showBoreder为 true 时,我们才需要应用borderModifier。如果直接使用上面代码,无论showBorder的值如何,borderModifier 都会被创建。为了优化这种情况,我们可以使用Modifier.lazy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kotlin复制代码@Composable
fun MyComponent(showBorder: Boolean) {
Box(
modifier = Modifier
.background(Color.Red)
.padding(16.dp)
.lazy {
if (showBorder) {
it.border(
width = 2.dp,
color = Color.Black
)
} else {
it
}
}
) {
// ...
}
}

通过Modifier.lazy,我们将条件判断的代码包装在一个 Lambda 表达式。只有在需要时,这个 Lambda 表达式才会被执行,从而避免了不必要的 Modifier 实例创建。

总得来说,合理使用 Modifier 对于提升应用性能至关重要。通过减少层级嵌套、避免不必要的重组以及合理使用Modifier.lazy,我们可以有效地控制渲染开销,为用户带来流畅的体验。在编写 Compose UI 代码时,请务必牢记这些优化技巧,让你的应用拥有优秀的性能表现。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

替代vim的神器:helix

发表于 2024-04-25

我这个月发现了一个文本编辑好命令工具,helix。该作者从Kakoune中获取灵感,用Rust实现,给人耳目一新的感觉。
先看看它的UI吧,比如打开一个文件夹:

image.png

编辑单个文件也很方便:

image.png

    1. 安装

helix是跨平台的命令文本编辑工具,不同的操作系统都可以安装对应版本。具体安装方式见官网:docs.helix-editor.com/install.htm…
对于Mac用户非常简单,直接运行brew命令进行安装:

1
复制代码brew install helix

安装完毕之后,如果直接在终端(我用的zsh)输出helix,可能会得到如下报错:

zsh: command not found: helix

后面一顿debug和搜索,发现Reddit上有人也遇到过这个问题,最后有人说:

brew install helix worked fine for me; I was able to run hx normally right afterwards. What “trouble running the program” did you have?

所以其实在MacOS里面,helix的命令是一个缩写“hx”,我在终端里面输入“hx”,果然执行成功。为了让已经习惯用helix作为命令的我不用再多记这个缩写,我在.zshrc文件中增加了helix命令别名,让它指向hx可执行文件地址:

1
bash复制代码alias helix="/opt/homebrew/bin/hx"

我个人是比较喜欢这个theme配色的,比较大气高雅。它完全兼容VIM的命令,所以作为多年的vim用户,可以平滑切换到helix。

    1. 配置和教程

而helix提供了直接可用的配置和功能,对于不想折腾vim插件的人真是太棒了。有人专门总结了helix的自定义配置方法和常用命令,我也在学习,非常实用:
www.cnblogs.com/tmacy/p/179…

    1. 语法支持

它有针对其他编程语言有支持,添加之后,如虎添翼啊:
docs.helix-editor.com/guides/addi…

这样就可以出现一些文档提示,不再只是支持一个简单的函数定义跳转。

  • 4.总结

总的来说,和VIM相比更加简单,增加很多快捷定义的指令,我个人觉得,常用的记一下,大部分还是用VIM的兼容命令更好。毕竟VIM是大部分服务器都会安装的文本编辑命令,记住它的命令和指令最实用。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…192021…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%