Skip to content

Conversation

@hossinasaadi
Copy link
Contributor

@hossinasaadi hossinasaadi commented Jan 4, 2026

This change can be implemented on other platforms as well (except Windows and WASM), but it has been tested only on Darwin (macOS) and iOS so far.
On iOS, the full CN geo file can now be loaded successfully without crashes or excessive memory usage.
On macOS, overall memory usage is reduced by approximately 20 MB based on local testing.

@hossinasaadi hossinasaadi marked this pull request as ready for review January 4, 2026 12:52
@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

似乎不是 #5480 (comment)

@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

这块我记得最开始是为了降低 CPU 占用
两个 dat 全部 cache 到内存里,等启动完成后清掉

后来有人反馈这样启动时内存峰值过高
因此你注释掉了 cache,加了几次强制 gc 缓解

不过一般使用场景下 geodat 高内存占用,根源还是由 domainmatcher 导致的

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

这块我记得最开始是为了降低 CPU 占用
两个 dat 全部 cache 到内存里,等启动完成后清掉

那真是个大聪明,启动耗时多一秒少一秒有啥区别

@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

那真是个大聪明,启动耗时多一秒少一秒有啥区别

😁


这个 PR 改成给 geodat 加索引文件,只读取部分 geodat 而非整个
理论上应该只能降低启动时的峰值内存才对

除非核心启动后的 gc 不能完全释放掉那些内存
这块我没测过

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

大聪明竟是我自己?我还以为你指的是全部 unmarshal

这个 PR 改成给 geodat 加索引文件,只读取部分 geodat 而非整个
理论上应该只能降低启动时的峰值内存才对

如果只是这样的话那就没必要了,iOS 的 50MB 限制都够加载两个 geo 文件

@RPRX RPRX closed this Jan 5, 2026
@hossinasaadi
Copy link
Contributor Author

大聪明竟是我自己?我还以为你指的是全部 unmarshal

这个 PR 改成给 geodat 加索引文件,只读取部分 geodat 而非整个
理论上应该只能降低启动时的峰值内存才对

如果只是这样的话那就没必要了,iOS 的 50MB 限制都够加载两个 geo 文件

I think there is a small misunderstanding here. This PR is not primarily about optimizing partial unmarshal for performance, but about solving a real memory-limit issue on iOS Packet Tunnel.

In practice on iOS:
• When using domain-list-community’s dlc.dat (≈2 MB),
Xray starts normally in the Packet Tunnel without exceeding memory limits.
• When using Loyalsoldier/v2ray-rules-dat’s geosite.dat (≈9 MB),
Xray crashes during startup in the Packet Tunnel, even with the same routing rules.

So the issue is not whether “50 MB should theoretically be enough”, but that on iOS Packet Tunnel the startup peak memory usage (protobuf unmarshal + runtime overhead + page cache) can exceed the system limit and cause the process to be killed immediately.

@hossinasaadi
Copy link
Contributor Author

这块我记得最开始是为了降低 CPU 占用 两个 dat 全部 cache 到内存里,等启动完成后清掉

后来有人反馈这样启动时内存峰值过高 因此你注释掉了 cache,加了几次强制 gc 缓解

不过一般使用场景下 geodat 高内存占用,根源还是由 domainmatcher 导致的

Yes, that’s correct. In practice the main issue appears before and during the construction of routing rules, not just during the initial geodat load.

When using a large geosite (for example geosite:cn, which expands to ~7000 domain entries), the process is roughly:
1. Load and unmarshal the geodat
2. Build a large []*RoutingRule from the geosite entries
3. Then build the DomainMatcher from those rules

During this phase, both the expanded routing rules and the domain matcher structures coexist in memory, which significantly increases peak memory usage. On iOS Packet Tunnel, this peak is often enough to exceed the system limit and cause the process to be killed.

This PR pervent keep []*RoutingRule in memory.

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

This PR pervent keep []*RoutingRule in memory.

如果只是这样的话还好,但 pre-map 似乎没必要吧?不是已经用上 mmap 了吗

最优解应该还是 #5480 (comment) ,有兴趣做一下吗?

@RPRX RPRX reopened this Jan 5, 2026
@hossinasaadi
Copy link
Contributor Author

如果只是这样的话还好,但 pre-map 似乎没必要吧?不是已经用上 mmap 了吗

My bad, this should be fixed now.

最优解应该还是 #5480 (comment) ,有兴趣做一下吗?

I may be missing something here, could you please clarify what you mean or give some guidance on how you’d suggest approaching this?

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

I may be missing something here, could you please clarify what you mean or give some guidance on how you’d suggest approaching this?

把 iOS 上 build 好的 DomainMatcher dump 到文件,然后用 mmap 读取并用 unsafe 转回 DomainMatcher,这个能实现吗

@hossinasaadi
Copy link
Contributor Author

I may be missing something here, could you please clarify what you mean or give some guidance on how you’d suggest approaching this?

把 iOS 上 build 好的 DomainMatcher dump 到文件,然后用 mmap 读取并用 unsafe 转回 DomainMatcher,这个能实现吗

This looks tricky, but I see it as a next approach. I will try to explore it further next week.

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

主要是因为还有切片啥的所以我不确定能不能完整 dump 出去,也就是说 DomainMatcher 的结构得改一下,改成完全固定的

@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

当前的流程是这样的:

  1. geodat 整个文件都被加载到内存里
  2. 只对需要的分类 unmarshal
  3. []*router.Domain 和 DomainMatchers 同时存在

因此改进的方向是 1 和 3
2 一直没有问题

此 PR 做的是 1
针对 1 不要直接完全加载到内存,能不能搞一个滑动窗口,浪费点 CPU 去做这个事情,而不是像此 PR 这样大动干戈
甚至直接让下游 ios 项目自己去切一下 geosite 用 core 自带的 ext: 就行了

针对 3 在循环里一边清理一边构建不就行了吗?

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

都是启动阶段峰值内存问题且 1 已经由 mmap 解决了 #5480 ,据 @hossinasaadi 说这个 PR 解决的是 3

@Fangliding
Copy link
Member

解决的似乎并不多
#5482 (comment)

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

说的是 mmap #5480 而不是 #5482

@Fangliding 话说你测一下这个 PR 现在的代码能降低峰值内存占用吗

@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

这个 pr 我没有仔细 review 等下我再看看
光看 desc 和部分代码是为了解决 1

mmap 不是滑动窗口,万一 seek 的分类比较靠后还是会加载很多,只能说缓解但不根治

@Fangliding
Copy link
Member

说的是 mmap #5480 而不是 #5482

我同时测了 5480和5482 的表现 只是comment在那而已 意思是 没想象的高(一百多MB的峰值少了六七MB)

@Fangliding 话说你测一下这个 PR 现在的代码能降低峰值内存占用吗

这是针对苹果平台的 所以我一直没想发表意见 if runtime.GOOS == "darwin" || runtime.GOOS == "ios"

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

@hossinasaadi 这个 PR 现在的代码是依赖 mmap 的吗

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

如果只是这样的话那就没必要了,iOS 的 50MB 限制都够加载两个 geo 文件

这些也是脑子不清醒时写的,我都忘了自己几天前合并了 mmap

@hossinasaadi
Copy link
Contributor Author

@hossinasaadi 这个 PR 现在的代码是依赖 mmap 的吗

Yes. It’s on mmap as far I checked.

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

那你改个更精确的标题吧,准备合了,新增那个文件放 common/platform/filesystem 就行,别嵌套太多文件夹

那个 transport/internet 我都想给它去掉

@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

geosite:xx@attrext: 会被破坏吗?

我对 core 配置加载和启动过程中各对象的生命周期没有足够了解
此 pr revert 后的代码不再专注 1
但也不是 3

3 是更激进地

这个 pr 应该是为了解决 config 对象被谁长期持有没法回收占用了内存
直接把 rr.xxx 给 nil 不行吗?

@hossinasaadi
Copy link
Contributor Author

geosite:xx@attrext: 会被破坏吗?

我对 core 配置加载和启动过程中各对象的生命周期没有足够了解 此 pr revert 后的代码不再专注 1 但也不是 3

3 是更激进地

这个 pr 应该是为了解决 config 对象被谁长期持有没法回收占用了内存 直接把 rr.xxx 给 nil 不行吗?

ext: i have been tested and works for geosite, geoip.
but the geosite:xx@attr now should be fixed. thanks

if we just rr.xxx = nil , seems api(update or add rule) will broken and wont update the list or may cause of other issues.

@hossinasaadi
Copy link
Contributor Author

@RPRX revert to if runtime.GOOS == "darwin" || runtime.GOOS == "ios" ?

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

只有 Windows 和 WASM 不支持 mmap 的话,不用 revert 了吧

解决一下 conflicts

@RPRX
Copy link
Member

RPRX commented Jan 5, 2026

然后标题也要改一下

@hossinasaadi hossinasaadi changed the title Routing: Implement lightweight routing Routing: reduce peak memory usage Jan 5, 2026
@hossinasaadi
Copy link
Contributor Author

只有 Windows 和 WASM 不支持 mmap 的话,不用 revert 了吧

解决一下 conflicts

No need to revert.

@RPRX RPRX changed the title Routing: reduce peak memory usage Routing: Reduce peak memory usage Jan 5, 2026
@RPRX RPRX merged commit c715154 into XTLS:main Jan 5, 2026
39 checks passed
@Meo597
Copy link
Collaborator

Meo597 commented Jan 5, 2026

if we just rr.xxx = nil , seems api(update or add rule) will broken and wont update the list or may cause of other issues.

光从代码看 api 的 reload 函数所需的 config.rr.xxx 是从函数传入的,不是对象的字段,似乎可以直接 rr.xxx = nil 不会有副作用

我没有用过 api 仅从代码推测

如果可以的话代码会更干净

@hossinasaadi hossinasaadi deleted the ios-routing branch January 6, 2026 00:01
@Tsukimeizi
Copy link

c715154遇到问题,会使用1.1.1.1解析所有出站,回退b38a412正常,访问国内地址使用223.5.5.5解析。

配置文件:

"dns": {
"servers": [
{
"tag": "dns-direct",
"address": "223.5.5.5",
"domains": ["geosite:cn"],
"skipFallback": true
},
{
"tag": "dns-proxy",
"address": "1.1.1.1"
}
],
"queryStrategy": "UseIPv4",
"disableFallbackIfMatch": true,
"serveStale": true,
"enableParallelQuery": true
},

@RPRX
Copy link
Member

RPRX commented Jan 6, 2026

@hossinasaadi

@hossinasaadi
Copy link
Contributor Author

c715154遇到问题,会使用1.1.1.1解析所有出站,回退b38a412正常,访问国内地址使用223.5.5.5解析。

配置文件:

"dns": { "servers": [ { "tag": "dns-direct", "address": "223.5.5.5", "domains": ["geosite:cn"], "skipFallback": true }, { "tag": "dns-proxy", "address": "1.1.1.1" } ], "queryStrategy": "UseIPv4", "disableFallbackIfMatch": true, "serveStale": true, "enableParallelQuery": true },

@Tsukimeizi Thanks for reporting this. Could you please confirm whether this PR resolves the issue?
#5499

@Tsukimeizi
Copy link

c715154遇到问题,会使用1.1.1.1解析所有出站,回退b38a412正常,访问国内地址使用223.5.5.5解析。
配置文件:
"dns": { "servers": [ { "tag": "dns-direct", "address": "223.5.5.5", "domains": ["geosite:cn"], "skipFallback": true }, { "tag": "dns-proxy", "address": "1.1.1.1" } ], "queryStrategy": "UseIPv4", "disableFallbackIfMatch": true, "serveStale": true, "enableParallelQuery": true },

@Tsukimeizi Thanks for reporting this. Could you please confirm whether this PR resolves the issue? #5499

It works well.

@hossinasaadi
Copy link
Contributor Author

I may be missing something here, could you please clarify what you mean or give some guidance on how you’d suggest approaching this?

把 iOS 上 build 好的 DomainMatcher dump 到文件,然后用 mmap 读取并用 unsafe 转回 DomainMatcher,这个能实现吗

@RPRX
I've implemented serialization for MphMatcherGroup and the memory usage decreased.
Same rules, This PR : 22 MB, Serialized : 14-15 MB

So far, I've done the following:
• Added a function to prebuild matchers from the config, keyed by ruleTag
• Serialized all rule matchers and saved them to a file
• Loaded the serialized data via mmap and applied it directly to routing conditions

This prebuild step may only be necessary on iOS. If so, we can guard it behind a flag and enable it only on iOS; otherwise, we can fall back to the normal loading path.

What needs to be add?

RPRX pushed a commit that referenced this pull request Jan 8, 2026
@RPRX
Copy link
Member

RPRX commented Jan 8, 2026

@hossinasaadi 没想到这么快就实现了,iOS 上 geo 文件可以随便用了,或许彻底解决了 iOS 上内存受限的问题,你先 PR 一下吧

开启方式就环境变量指定临时文件夹 path 吧,Linux 硬路由上也需要这个,其它内存不受限的场景倒没必要,把规则放内存中更快

有了 PR 后你们测试一下 @iambabyninja @mangustyura

@RPRX
Copy link
Member

RPRX commented Jan 8, 2026

@Meo597 我仔细查了下 mmap 确实是懒加载,所以说它有没有类似 swap 的机制,就是说读完没用的不再占用运行内存

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants