Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

希望能够优化一下内存占用 #68

Closed
timi-owo opened this issue Dec 13, 2020 · 80 comments
Closed

希望能够优化一下内存占用 #68

timi-owo opened this issue Dec 13, 2020 · 80 comments

Comments

@timi-owo
Copy link

如题,从古老的v2ray 4.22.1版本升级过来的,几乎同样配置的情况下,
之前的版本开机占用≈10m,现在的版本开机占用>60m,一段时间后才能勉强恢复正常(<20m)。
windows-amd64平台,作为客户端,服务器上(linux-amd64)也有内存占用过大的问题。

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

这应该是默认 jsonem 导致的,你试试 v2ray-core v4.32.0+

不过之前的相关报告大多来自华为的 arm64,amd64 上还是第一次听说

Linux x64 上,我们的测试结果是启动占 20MB

@timi-owo
Copy link
Author

刚启动(有点恐怖):
1
一段时间后(GC?):
2

这是刚测试的,配置文件一样,三个都没有跑过流量。
其实这点内存没什么感觉,只是刚启动的时候有点离谱。
貌似要等一轮GC才能释放掉内存?不了解GO语言,瞎猜的。

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

看看 v2ray-core v4.29.x

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

@RPRX 其实直接回复 BOT 好像也能回复

@timi-owo
Copy link
Author

看看 v2ray-core v4.29.x

4.29.0没有问题,跟4.22.1表现一样。

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

看看 v2ray-core v4.31.3,这是默认 jsonem 前的最后一个版本

@timi-owo
Copy link
Author

看看 v2ray-core v4.31.3,这是默认 jsonem 前的最后一个版本

4.31.3也没有问题。

@badO1a5A90
Copy link
Member

@RPRX 如果amd64架构也会出现的话,会不会是和jsonem加载某些设置有关.

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

看起来默认 jsonem 前调用 v2ctl 是总体瞬时占内存但很快释放,默认 jsonem 后就要等 GC,等下传一个手动调用 GC 的版本

@g552656
Copy link

g552656 commented Dec 13, 2020

我也有一个内存占用的问题一直没看到有人说以为只有我遇到
centos系统装的服务端,id比较多,自从更新4.28之后的版本,启动之后内存占用会一直不断增加,2G内存总量会一直占到1.8G,更新之前是用4.22.1版本,同样的用户数目,最高800M
在无数机器上测试,但都是centos7 64bit测试的,可以确定肯定是v2ray-core更新前后的区别
不知道是否有解。

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

Xray-core/main/run.go

Lines 81 to 82 in decb012

// Explicitly triggering GC to remove garbage from config loading.
runtime.GC()

于是我发现,现在的代码中加载配置后已经主动触发 GC 了

@SekiBetu
Copy link

SekiBetu commented Dec 13, 2020

跟GC肯定没啥关系的呀,就算调用GC,你申请过程也是瞬时很大的,还是要在代码中找哪个地方申请的多,比如说,以前阿里的Java开发手册里就有一个例子,并不是说肯定是类似的,只是想说可能哪里有遗漏的死角有申请大量内存
image

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

@SekiBetu 比较奇怪的事情就是这部分内存最终会被释放,也不是内存泄漏。切片是一种可能,但配置文件占不了那么多内存。

@RPRX
Copy link
Member

RPRX commented Dec 13, 2020

@RPRX 明天我调试一下 Xray 看看吧

@vangork
Copy link

vangork commented Dec 14, 2020

@RPRX 应该是读取配置文件的问题。我在红米ac2100(CPU:MT7621, RAM:128MB)上如果用xray读取json配置文件启动,一会就out of memory被killed了,如果把json配置文件转成pb格式,启动时消耗的内存明显要小不少并可以成功启动,所以应该是在parse json文件的时候占用的内存较大。

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

加了行 debug.FreeOSMemory(),看看还有没有加载 JSON 后释放内存不及时的问题

Xray-windows-64.zip
Xray-linux-64.zip
Xray-linux-arm64-v8a.zip
Xray-linux-mips32le.zip

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

Xray-core/main/run.go

Lines 81 to 82 in decb012

// Explicitly triggering GC to remove garbage from config loading.
runtime.GC()

于是我发现,现在的代码中加载配置后已经主动触发 GC 了

在这行后加的,原因是 runtime.GC() 似乎只是回收垃圾,并没有把内存返还给操作系统

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

这边进行了一些测试,发现问题不在 JSON,在 geofile 上,详情如下:

我在 Windows 上用简单的配置运行 Xray,观察到它只占了不到 5MB 的内存(群友也是这样),与你的情况完全不同。结合之前一直有的疑惑“加载个 JSON 为什么占那么多内存?”,遂想到 jsonem 还有加载转换 geofile 的功能,于是简单改了改配置以使用它们,可以看到此时 Xray 刚启动时会占较多的内存,并逐渐减到 10MB 多,与你的情况大致相符。

若使用我刚刚上传的 debug.FreeOSMemory() 版本,即使加载 geofile,也观察不到瞬时的内存占用。它仍然存在,但观察不到,就像以前版本的 v2ray 调用 v2ctl 一样:v2ctl 退出后,它占的内存会返还给操作系统,只是这一整个过程快到观察不到而已。

而对于接受不了瞬时内存占用的设备,比如一些硬路由,建议使用传统方式:先转 pb。我会在下个版本的 release note 中详细说明。

@badO1a5A90
Copy link
Member

@RPRX 破案了....

@vangork
Copy link

vangork commented Dec 14, 2020

@RPRX 确实,如果不配置routing,内存使用较小,如果routing里加载了geofile的话在parse的阶段就out of memory了。感谢debug。

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

@vangork 试试刚刚上传的测试版本,直接加载 json 和 .dat 还会不会被 kill

@timi-owo
Copy link
Author

加了行 debug.FreeOSMemory(),看看还有没有加载 JSON 后释放内存不及时的问题

Xray-windows-64.zip
Xray-linux-64.zip
Xray-linux-arm64-v8a.zip
Xray-linux-mips32le.zip

此版本没有问题了。

@vangork
Copy link

vangork commented Dec 14, 2020

@vangork 试试刚刚上传的测试版本,直接加载 json 和 .dat 还会不会被 kill

@RPRX ,试了,依旧会被kill,估计是128MB RAM不够用。
root@RM2100:/usr/local/xray# ./xray_softfloat
Xray 1.1.3 (Xray, Penetrates Everything.) Custom (go1.15.6 linux/mipsle)
A unified platform for anti-censorship.
2020/12/14 06:41:14 Using default config: /usr/local/xray/config.json
2020/12/14 06:41:14 [Info] main/jsonem: Reading config: /usr/local/xray/config.json
Killed

在运行xray之前,路由有76MB的空闲RAM。
Mem: 44856K used, 76988K free, 388K shrd, 1512K buff, 3048K cached
CPU: 0% usr 0% sys 0% nic 98% idle 0% io 0% irq 0% sirq
Load average: 2.93 2.13 1.09 2/323 1605
PID PPID USER STAT VSZ %VSZ %CPU COMMAND

成功运行xray加载pb格式的配置文件并平稳运行后,内存剩余24MB,可见xray占用52MB左右RAM。
Mem: 97684K used, 24160K free, 388K shrd, 1412K buff, 12256K cached
CPU: 0% usr 0% sys 0% nic 100% idle 0% io 0% irq 0% sirq
Load average: 0.07 0.06 0.12 2/89 2210
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
2210 1048 root R 1256 1% 0% top
1981 1 root S 664m 558% 0% /usr/local/xray/xray -config /usr/local/xray/config.pb -format pb

刷的openwrt官方的snapshot版本没有swap分区。
root@RM2100:/usr/local/xray# df -h
Filesystem Size Used Available Use% Mounted on
/dev/root 3.3M 3.3M 0 100% /rom
tmpfs 59.5M 388.0K 59.1M 1% /tmp
/dev/ubi0_1 97.8M 20.2M 72.9M 22% /overlay
overlayfs:/overlay 97.8M 20.2M 72.9M 22% /
tmpfs 512.0K 0 512.0K 0% /dev

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

@vangork 好的,看来这种设备上需要先转 pb 了

@timi-owo
Copy link
Author

这边进行了一些测试,发现问题不在 JSON,在 geofile 上,详情如下:

我在 Windows 上用简单的配置运行 Xray,观察到它只占了不到 5MB 的内存(群友也是这样),与你的情况完全不同。结合之前一直有的疑惑“加载个 JSON 为什么占那么多内存?”,遂想到 jsonem 还有加载转换 geofile 的功能,于是简单改了改配置以使用它们,可以看到此时 Xray 刚启动时会占较多的内存,并逐渐减到 10MB 多,与你的情况大致相符。

若使用我刚刚上传的 debug.FreeOSMemory() 版本,即使加载 geofile,也观察不到瞬时的内存占用。它仍然存在,但观察不到,就像以前版本的 v2ray 调用 v2ctl 一样:v2ctl 退出后,它占的内存会返还给操作系统,只是这一整个过程快到观察不到而已。

而对于接受不了瞬时内存占用的设备,比如一些硬路由,建议使用传统方式:先转 pb。我会在下个版本的 release note 中详细说明。

不过我还是很疑惑为什么转换JSON和GEOFILE需要申请这么多内存......

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

@timi-owo 只是加载转换 geofile 的问题,与 JSON 无关

@timi-owo
Copy link
Author

这边进行了一些测试,发现问题不在 JSON,在 geofile 上,详情如下:
我在 Windows 上用简单的配置运行 Xray,观察到它只占了不到 5MB 的内存(群友也是这样),与你的情况完全不同。结合之前一直有的疑惑“加载个 JSON 为什么占那么多内存?”,遂想到 jsonem 还有加载转换 geofile 的功能,于是简单改了改配置以使用它们,可以看到此时 Xray 刚启动时会占较多的内存,并逐渐减到 10MB 多,与你的情况大致相符。
若使用我刚刚上传的 debug.FreeOSMemory() 版本,即使加载 geofile,也观察不到瞬时的内存占用。它仍然存在,但观察不到,就像以前版本的 v2ray 调用 v2ctl 一样:v2ctl 退出后,它占的内存会返还给操作系统,只是这一整个过程快到观察不到而已。
而对于接受不了瞬时内存占用的设备,比如一些硬路由,建议使用传统方式:先转 pb。我会在下个版本的 release note 中详细说明。

不过我还是很疑惑为什么转换JSON和GEOFILE需要申请这么多内存......

我猜可能是把5m大小的geoip文件加载进内存,然后再给富裕的内存空间来进行转换吧

可能是吧,问题应该就在于 GC 后没有立即 FREE 掉还给 OS 导致部分内存有限的设备容易 OOM

猜测是 Go 内存管理里的一种内存池机制,不过好在可以 debug.FreeOSMemory() 但还是有瞬时内存占用。

刚试图看了一下 geofile 结构和相关路由模块的代码,发现有点读不懂哈哈,等我学会了 Go 再来贡献代码吧。

我觉得 geofile 有改进空间,或许可以不用 protobuf,但牵扯到的相关模块也要改。

还要考虑日渐增长的数据量和 routing 处理性能,以及历史数据的升级,改动量应该不小,还是个 breaking change。

目前大佬们的重心还是在协议和传输这一块,毕竟这个才是 Project V / Project X 的灵魂。

真的很感谢这个伟大的项目,除了用来翻墙还有很多实用的地方,简直神器。

@RPRX
Copy link
Member

RPRX commented Dec 14, 2020

由于 geoip.dat 比较大,所以我特意去看了下加载它的代码(也不至于占上百 MB 吧?),果然发现了问题:

func toCidrList(ips StringList) ([]*router.GeoIP, error) {
var geoipList []*router.GeoIP
var customCidrs []*router.CIDR
for _, ip := range ips {
if strings.HasPrefix(ip, "geoip:") {
country := ip[6:]
geoip, err := loadGeoIP(strings.ToUpper(country))
if err != nil {
return nil, newError("failed to load GeoIP: ", country).Base(err)
}
geoipList = append(geoipList, &router.GeoIP{
CountryCode: strings.ToUpper(country),
Cidr: geoip,
})
continue
}

func loadGeoIP(country string) ([]*router.CIDR, error) {
return loadIP("geoip.dat", country)
}
func loadIP(filename, country string) ([]*router.CIDR, error) {
geoipBytes, err := filesystem.ReadAsset(filename)
if err != nil {
return nil, newError("failed to open file: ", filename).Base(err)
}
var geoipList router.GeoIPList
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
return nil, err
}
for _, geoip := range geoipList.Entry {
if geoip.CountryCode == country {
return geoip.Cidr, nil
}
}
return nil, newError("country not found in ", filename, ": ", country)
}

对于路由规则内的每一个 geoip:,都会从头加载 5MB 的 geoip.dat 并 Unmarshal + 遍历。

不用怀疑,geosite: 我也看了,也是这样。 对于这两个,都是你写多少规则就重复多少次,你们可以验证一下。

所以,我决定重写这部分代码。

@g552656
Copy link

g552656 commented Dec 14, 2020

请问对服务端也有效吗?
现在服务端加载用户多了会慢慢占用到接近2G内存

@vangork
Copy link

vangork commented Dec 15, 2020

@RPRX 感谢,可以使用。

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

@vangork 有观察到瞬时内存占多少吗

@SekiBetu
Copy link

Snipaste_2020-12-15_17-56-26
Snipaste_2020-12-15_17-56-37
Snipaste_2020-12-15_17-56-46
Snipaste_2020-12-15_17-59-11
Snipaste_2020-12-15_17-59-29
Snipaste_2020-12-15_18-02-57
Snipaste_2020-12-15_18-04-51
这边提供这个方法来监测,刷新率只能降到1秒一次,还是有点误差的

@SekiBetu
Copy link

SekiBetu commented Dec 15, 2020

image
更新:上述的Working Set更换为Private Bytes的结果和任务管理器的结果一模一样
测试的时候最好是停一段时间再开xray,这样结果更加一致,像图里第二段我就急了,关了后立马开了,就出现不了第一次加载时候的峰值(也可能是因为1秒钟的频率错开了峰值,反正多试几次取最高值即可)
此图调整过显示比例和显示的形式,右键属性就可以调

我肉眼观察得蛮准的,说是100mb,结果就是100mb

@vangork
Copy link

vangork commented Dec 15, 2020

@RPRX
无缓存版本
root@RM2100:~# cat /proc/8787/status
VmPeak: 680872 kB
VmSize: 680872 kB
VmHWM: 48272 kB
VmRSS: 48272 kB

当前版本
root@RM2100:~# cat /proc/8868/status
VmPeak: 681136 kB
VmSize: 681136 kB
VmHWM: 61664 kB
VmRSS: 55224 kB

PS: From: https://ewx.livejournal.com/579283.html
VmPeak and VmSize are the peak/current total address space used. In other words, the sum of all virtual memory areas in the process, whatever they are used for.
VmHWM and VmRSS are the process’s peak/current usage of physical RAM.

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

@SekiBetu 你试试这个版本 #68 (comment)

@SekiBetu
Copy link

SekiBetu commented Dec 15, 2020

@SekiBetu 你试试这个版本 #68 (comment)

image
比起第四个版本第三个版本的后两次测试明显大幅降低了内存占用,和我刚才测出来一样玄学,首次启动100mb,后面几次启动就掉下去了

我又拿第四个版本过来测了一遍
image
也是如此,第一次占用高,后两次又变小了

我的想法是,取最高值,因为采样率是一秒一次,很可能后两次没有采到最高峰,采到了上升期或者下降期的70mb的数值
图上是各点连接的拟合图像,实际是离散的每秒一个数据
linux上的测试大家结果不同也有可能是采样率没采到最高峰,现在的linux测试结果都是从已经运行的程序中测试的吧,没有在程序未运行就开始监控了?

所以我的结论是第三个版本第四个版本在启动内存占用上和1.1.3版本没有区别

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

@SekiBetu 结论不正确,我的猜想是你用到了几乎所有规则(比如除 CN 外),所以没有明显区别,这取决于具体的配置文件

@SekiBetu
Copy link

SekiBetu commented Dec 15, 2020

@SekiBetu 结论不正确,我的猜想是你用到了几乎所有规则(比如除 CN 外),所以没有明显区别,这取决于具体的配置文件

我用的规则配置是geoip:cn、private和geosite:cn、private是direct,然后geosite:category-ads-all是block
文件是增强版的geo文件 https://github.com/Loyalsoldier/v2ray-rules-dat/releases

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

@SekiBetu 试试下面的版本,因为不太科学(或许是 CN 占很大一部分?)

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

新的最终版:默认关闭的缓存机制 + 预解析 pb 结构 #68 (comment) 只 Unmarshal 需要的部分 + 多次主动 GC + FreeOSMemory

这个版本应该最省内存了,望得到广泛测试。如果没有问题,等下提交代码。

Xray-android-arm64-v8a.zip
Xray-linux-64.zip
Xray-linux-arm64-v8a.zip
Xray-linux-mips32le.zip
Xray-windows-64.zip

@defead
Copy link

defead commented Dec 15, 2020

新的最终版:默认关闭的缓存机制 + 预解析 pb 结构 #68 (comment) 只 Unmarshal 需要的部分 + 多次主动 GC + FreeOSMemory

这个版本应该最省内存了,望得到广泛测试。如果没有问题,等下提交代码。

Xray-android-arm64-v8a.zip
Xray-linux-64.zip
Xray-linux-arm64-v8a.zip
Xray-linux-mips32le.zip
Xray-windows-64.zip

mips32le 占用内存 16M左右,比之前好多了

@SekiBetu
Copy link

SekiBetu commented Dec 15, 2020

image
截到峰值的概率变小了
image
这边丢人了,我发现峰值的产生应该是我没有关闭网页、QQ、Telegram等软件测试的结果,我又扔到windows沙盒中去测试,结果是很正常的从小到大,而没有峰值,所以release note可以注释一下,
小内存的路由器重启core时尽量设备不要同时连接多个网页,会在重启core后短时间内触发数个重连导致瞬时内存变大

@vangork
Copy link

vangork commented Dec 15, 2020

@RPRX 完美
VmPeak: 680624 kB
VmSize: 680624 kB
VmHWM: 46388 kB
VmRSS: 46388 kB

@RPRX
Copy link
Member

RPRX commented Dec 15, 2020

GitHub 上和 Project X 群反馈均很理想,代码已提交 45f44c4#68 (comment) 的文件均 Reproducible

这大幅降低了启动时的瞬时内存占用,并小幅降低了启动后的最终内存占用。

感谢此 issue 及各位的多次测试,现在解决了默认内部加载配置带来的问题,预解析 pb 结构更是让更多的设备可以直接加载配置。

@RPRX RPRX mentioned this issue Dec 15, 2020
@badO1a5A90
Copy link
Member

测试结果完美,结案,撒花.

@niuniudada
Copy link

期待发版!我的小内存arm开发板,正遇到这个问题.

@ghost
Copy link

ghost commented Dec 17, 2020

64M RAM WORK

@RPRX
Copy link
Member

RPRX commented Dec 17, 2020

64M RAM WORK

想问问以前的版本也可以吗?

@RPRX
Copy link
Member

RPRX commented Dec 19, 2020

@RPRX RPRX closed this as completed Dec 19, 2020
@muziling
Copy link

24047 1 proxy S 4803m 994% 0% xray -c /root/xray.json

这个问题有完全解决了吗?x64路由跑了13天, 占用近5G。

xray version

Xray 1.5.3 (Xray, Penetrates Everything.) OpenWrt (go1.17.6 linux/amd64)
A unified platform for anti-censorship.

@Mr-xn
Copy link

Mr-xn commented Mar 22, 2022

docker 里一样 out of memory killed process xxxx (xray) total-vm:6627100KB,anon-rss:253404KB

mwhorse46 added a commit to mwhorse46/Xray-core that referenced this issue Feb 19, 2023
@realerikk0
Copy link

x64一样, /tmp/etc/passwall/bin/xray run -c /tmp/etc/passwall/TCP_SOCKS.json消耗了全部的memory,4GB,内存占用肉眼可见增加

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

No branches or pull requests