任何人都可以告诉我为什么我无法通过以下方式成功testingR中OpenBLAS的dgemm性能(在dgemm中)?
用OpenBLAS库libopenblas.so编译我的C程序libopenblas.so
将生成的共享库mmperf.so到R中,调用R wrapper函数mmperf并报告dgemm性能。
第1点看起来很奇怪,但我别无select,因为我想在我想testing的机器上没有root权限,所以实际连接到OpenBLAS是不可能的。 通过“不成功”,我的意思是我的程序最终报告dgemm性能参考BLAS而不是OpenBLAS。 我希望有人能向我解释:
为什么我的方式不行;
是否有可能完成工作(这很重要,因为如果不可能的话,我必须写一个C的mainfunction,并在C程序中完成我的工作。)
我已经调查了两天这个问题,在这里我将包括各种系统输出,以协助您做出诊断。 为了使事情具有可重现性,我还将包括代码,makefile以及shell命令。
第1部分:testing前的系统环境
运送带有共享库的GNU / Linux Firefox插件(用于安装时无root权限)
创build静态库时embedded所有外部引用
在Linux的哪个库中是系统调用的,这个库如何链接到包含系统调用的可执行对象文件?
dlsym()在C ++中的全局variables
在参数typedef更改时重builddynamic库
有两种方法可以调用R,使用R或Rscript 。 调用时加载的内容存在一些差异:
~/Desktop/dgemm$ readelf -d $(R RHOME)/bin/exec/R | grep "NEEDED" 0x00000001 (NEEDED) Shared library: [libR.so] 0x00000001 (NEEDED) Shared library: [libpthread.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] ~/Desktop/dgemm$ readelf -d $(R RHOME)/bin/Rscript | grep "NEEDED" 0x00000001 (NEEDED) Shared library: [libc.so.6]
这里我们需要selectRscript ,因为R加载libR.so ,它会自动加载引用BLAS libblas.so.3 :
~/Desktop/dgemm$ readelf -d $(R RHOME)/lib/libR.so | grep blas 0x00000001 (NEEDED) Shared library: [libblas.so.3] ~/Desktop/dgemm$ ls -l /etc/alternatives/libblas.so.3 ... 31 May /etc/alternatives/libblas.so.3 -> /usr/lib/libblas/libblas.so.3.0 ~/Desktop/dgemm$ readelf -d /usr/lib/libblas/libblas.so.3 | grep SONAME 0x0000000e (SONAME) Library soname: [libblas.so.3]
相比之下, Rscript提供了一个更清洁的环境。
第2部分:OpenBLAS
从OpenBLAS下载源文件和一个简单的make命令后,就可以生成libopenblas-<arch>-<release>.so-<version>forms的共享库。 请注意,我们将不具有root权限来安装它; 相反,我们将这个库复制到我们的工作目录~/Desktop/dgemm ,并将其重命名为libopenblas.so 。 同时,我们必须制作名为libopenblas.so.0另一个副本,因为这是运行时加载程序将要寻找的SONAME :
~/Desktop/dgemm$ readelf -d libopenblas.so | grep "RPATH|SONAME" 0x0000000e (SONAME) Library soname: [libopenblas.so.0]
请注意, RPATH属性没有给出,这意味着这个库是打算放在/usr/lib ,我们应该调用ldconfig将它添加到ld.so.cache 。 但是,我们再次没有root权限来执行此操作。 事实上,如果能做到这一点,那么所有的困难都没有了。 然后我们可以使用update-alternatives --config libblas.so.3来有效地将R链接到OpenBLAS。
这里是一个C脚本mmperf.c计算mmperf.c乘以2平方的matrix大小为N :
#include <Rh> #include <Rmath.h> #include <Rinternals.h> #include <R_ext/BLAS.h> #include <sys/time.h> /* standard C subroutine */ double mmperf (int n) { /* local vars */ int n2 = n * n,tmp; double *A,*C,one = 1.0; struct timeval t1,t2; double elapsedtime,GFLOPs; /* simulate N-by-N matrix A */ A = (double *)calloc(n2,sizeof(double)); GetRNGstate(); tmp = 0; while (tmp < n2) {A[tmp] = runif(0.0,1.0); tmp++;} PutRNGstate(); /* generate N-by-N zero matrix C */ C = (double *)calloc(n2,sizeof(double)); /* time 'dgemm.f' for C <- A * A + C */ gettimeofday(&t1,NULL); F77_CALL(dgemm) ("N","N",&n,&one,A,C,&n); gettimeofday(&t2,NULL); /* free memory */ free(A); free(C); /* compute and return elapsedtime in microseconds (usec or 1e-6 sec) */ elapsedtime = (double)(t2.tv_sec - t1.tv_sec) * 1e+6; elapsedtime += (double)(t2.tv_usec - t1.tv_usec); /* convert microseconds to nanoseconds (1e-9 sec) */ elapsedtime *= 1e+3; /* compute and return GFLOPs */ GFLOPs = 2.0 * (double)n2 * (double)n / elapsedtime; return GFLOPs; } /* R wrapper */ SEXP R_mmperf (SEXP n) { double GFLOPs = mmperf(asInteger(n)); return ScalarReal(GFLOPs); }
这里是一个简单的R脚本mmperf.R来报告情况N = 2000 mmperf.R
mmperf <- function (n) { dyn.load("mmperf.so") GFLOPs <- .Call("R_mmperf",n) dyn.unload("mmperf.so") return(GFLOPs) } GFLOPs <- round(mmperf(2000),2) cat(paste("GFLOPs =",GFLOPs,"n"))
最后还有一个简单的makefile生成共享库mmperf.so :
mmperf.so: mmperf.o gcc -shared -L$(shell pwd) -Wl,-rpath=$(shell pwd) -o mmperf.so mmperf.o -lopenblas mmperf.o: mmperf.c gcc -fpic -O2 -I$(shell Rscript --default-packages=base --vanilla -e 'cat(R.home("include"))') -c mmperf.c
把所有这些文件放在工作目录~/Desktop/dgemm ,然后编译它:
~/Desktop/dgemm$ make ~/Desktop/dgemm$ readelf -d mmperf.so | grep "NEEDED|RPATH|SONAME" 0x00000001 (NEEDED) Shared library: [libopenblas.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [/home/zheyuan/Desktop/dgemm]
输出使我们确信OpenBLAS已正确链接,并且正确设置了运行时加载path。
第4部分:在R中testingOpenBLAS
让我们做
~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
注意我们的脚本只需要R中的base包,而--vanilla则用来忽略R启动时的所有用户设置。 在我的笔记本上,我的程序返回:
GFLOPs = 1.11
哎呀! 这是真正的参考BLAS性能不OpenBLAS(这是约8-9 GFLOPs)。
第5部分:为什么?
说实话,我不知道为什么会发生这种情况。 每一步似乎都能正常工作。 当调用R时是否会有微妙的变化? 例如,出于某种原因某种程度上,OpenBLAS库被BLAS忽略的可能性? 任何解释和解决scheme? 谢谢!
是否有一个多操作系统蓝牙库?
在共享库的构build选项中添加“-rpath,/ usr / lib”会导致段错误
检查非默认加载器的共享库
dynamic加载(DL)库和第一条指令
多余的图书馆联系
为什么我的方式不起作用
首先,UNIX上的共享库被设计为模仿归档库的工作方式(归档库在那里)。 特别是,如果你有libfoo.so和libbar.so ,都定义了符号foo ,那么无论哪个库首先被加载的都是获胜的:从程序中的任何地方(包括来自libbar.so )的所有对foo引用将绑定到libfoo.so的foo的定义。
这模仿了如果你将程序与libfoo.a和libbar.a联系起来的libbar.a ,这两个档案库都定义了相同的符号foo 。 更多信息链接在这里 。
从上面应该清楚的是,如果libblas.so.3和libopenblas.so.0定义了相同的一组符号(它们是这样做的 ),并且如果libblas.so.3加载到进程中,那么libopenblas.so.0 永远不会被调用。
其次,你已经正确地决定,由于R直接链接到libR.so ,并且由于libR.so直接链接到libblas.so.3 ,所以保证了libopenblas.so.0将会失败。
然而,你错误地认为Rscript是更好的,但它不是: Rscript是一个很小的二进制文件(我的系统是11K;相比之下, libR.so是2.4MB),大概所有它是R exec 。 在strace输出中看到这是微不足道的:
strace -e trace=execve /usr/bin/Rscript --default-packages=base --vanilla /dev/null execve("/usr/bin/Rscript",["/usr/bin/Rscript","--default-packages=base","--vanilla","/dev/null"],[/* 42 vars */]) = 0 execve("/usr/lib/R/bin/R",["/usr/lib/R/bin/R","--slave","--no-restore","--file=/dev/null","--args"],[/* 43 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_code=CLD_EXITED,si_pid=89625,si_status=0,si_utime=0,si_stime=0} --- --- SIGCHLD {si_signo=SIGCHLD,si_pid=89626,si_stime=0} --- execve("/usr/lib/R/bin/exec/R",["/usr/lib/R/bin/exec/R",[/* 51 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=89630,si_stime=0} --- +++ exited with 0 +++
这意味着当你的脚本开始执行的时候, libblas.so.3已经被加载了, libopenblas.so.0将作为mmperf.so一个依赖被加载, 实际上不会被用于任何事情。
是否有可能使其工作
大概。 我可以想到两个可能的解决方案:
假装libopenblas.so.0实际上是libblas.so.3
根据libopenblas.so重建整个R包。
对于#1,你需要在ln -s libopenblas.so.0 libblas.so.3 , 然后通过适当地设置LD_LIBRARY_PATH来确保在系统之前找到你的libblas.so.3副本。
这似乎为我工作:
mkdir /tmp/libblas # pretend that libc.so.6 is really libblas.so.3 cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/libblas/libblas.so.3 LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null Error in dyn.load(file,DLLpath = DLLpath,...) : unable to load shared object '/usr/lib/R/library/stats/libs/stats.so': /usr/lib/liblapack.so.3: undefined symbol: cgemv_ During startup - Warning message: package 'stats' in options("defaultPackages") was not found
注意我是如何得到一个错误的(我的“假装” libblas.so.3没有定义符号,因为它实际上是libc.so.6的一个副本)。
你也可以通过这种方式来确认libblas.so.3哪个版本正在被加载:
LD_DEBUG=libs LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null |& grep 'libblas.so.3' 91533: find library=libblas.so.3 [0]; searching 91533: trying file=/usr/lib/R/lib/libblas.so.3 91533: trying file=/usr/lib/x86_64-linux-gnu/libblas.so.3 91533: trying file=/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server/libblas.so.3 91533: trying file=/tmp/libblas/libblas.so.3 91533: calling init: /tmp/libblas/libblas.so.3
对于#2,你说:
我想要测试的机器上没有root权限,所以实际链接到OpenBLAS是不可能的。
但这似乎是一个虚假的说法:如果你可以建立libopenblas ,当然你也可以建立自己的R版本。
更新:
你在开头提到libblas.so.3和libopenblas.so.0定义了相同的符号,这是什么意思? 他们有不同的SONAME,不足以通过系统区分他们吗?
符号和SONAME没有任何关系。
您可以在readelf -Ws libblas.so.3和readelf -Ws libopenblas.so.0的输出中看到符号。 与BLAS相关的符号(如cgemv_ )将出现在两个库中。
你对SONAME 可能来自Windows。 Windows上的DLL是完全不同的。 特别是,当FOO.DLL从FOO.DLL导入符号bar时,符号( bar )的名称和导入该符号的DLL ( BAR.DLL )都将记录在FOO.DLL的导入表中。
这样可以很容易让R从BLAS.DLL导入cgemv_ ,而MMPERF.DLL从MMPERF.DLL导入相同的符号。
但是,这使得图书馆很难进行插入 ,并且与存档库的工作方式(即使在Windows上)完全不同。
对于哪个设计总体来说,意见不一,但是这两个系统都不可能改变它的模型。
UNIX可以模拟Windows风格的符号绑定:请参阅dlopen 手册页中的 RTLD_DEEPBIND 。 注意:这些是充满危险的,可能会混淆UNIX专家,没有被广泛使用,并可能有实施错误。
更新2:
你的意思是我编译R并将其安装在我的主目录下?
是。
那么当我想调用它的时候,我应该明确地给出我的版本的可执行程序的路径,否则系统上的那个可能会被调用呢? 或者,我可以把这个路径放在环境变量$ PATH的第一个位置来欺骗系统吗?
无论哪种方式工作。
*********************
解决方案1:
*********************
谢谢俄罗斯人 ,我的问题终于解决了。 调查需要Linux系统调试和补丁方面的重要技能,我相信这是我学到的一笔宝贵的财富。 在这里,我会张贴一个解决方案,以及纠正我原来的职位的几点。
1关于调用R
在我原来的文章中,我提到有两种方法可以通过R或Rscript来启动R。 但是,我错误地夸大了他们的差异。 现在让我们通过一个重要的Linux调试工具strace来调查他们的启动过程(请参阅man strace )。 在shell中输入一个命令后,实际上会发生很多有趣的事情,我们可以使用
strace -e trace=process [command]
跟踪涉及流程管理的所有系统调用。 因此,我们可以观察流程的分叉,等待和执行步骤。 虽然在手册页中没有说明,但是@Employed Russian显示只能指定process的子类,例如execve执行步骤。
对于R我们有
~/Desktop/dgemm$ time strace -e trace=execve R --vanilla < /dev/null > /dev/null execve("/usr/bin/R",["R","--vanilla"],[/* 70 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=5777,[/* 79 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=5778,si_stime=0} --- +++ exited with 0 +++ real 0m0.345s user 0m0.256s sys 0m0.068s
而对于Rscript我们有
~/Desktop/dgemm$ time strace -e trace=execve Rscript --default-packages=base --vanilla /dev/null execve("/usr/bin/Rscript",["Rscript",[/* 70 vars */]) = 0 execve("/usr/lib/R/bin/R","--file=/dev/null"],[/* 71 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=5822,si_pid=5823,[/* 80 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=5827,si_stime=0} --- +++ exited with 0 +++ real 0m0.063s user 0m0.020s sys 0m0.028s
我们也用time来衡量启动时间。 注意
Rscript比R快5.5倍。 一个原因是R将在启动时加载6个默认软件包,而Rscript只能通过控制加载一个base软件包:– --default-packages=base 。 但即使没有这个设置,它仍然快得多。
最后,两个启动过程都被引导到$(R RHOME)/bin/exec/R ,在我原来的文章中,我已经利用readelf -d来显示这个可执行文件会加载libR.so ,与libblas.so.3 。 根据@Employed俄罗斯的解释,首先加载的BLAS库会赢,所以我的原始方法将无法工作。
为了成功运行strace ,我们使用了惊人的文件/dev/null作为输入文件并在必要时输出文件。 例如, Rscript需要一个输入文件,而R需要这两个文件。 我们将null设备提供给这两者,以使命令顺利运行并且输出干净。 空设备是一个物理上存在的文件,但令人惊讶的。 读它时,它什么也没有; 写信给它,它丢弃一切。
2.作弊R
既然libblas.so会被加载,我们唯一能做的就是提供我们自己的这个库的版本。 正如我在原文中所说的,如果我们拥有root权限,通过使用update-alternatives --config libblas.so.3 ,这非常容易,这样系统Linux将帮助我们完成这个转换。 但@Employed俄罗斯提供了一个非常好的方式来欺骗没有root访问权限的系统: 让我们来看看R如何在启动时发现BLAS库,并确保在发现系统默认值之前提供我们的版本! 要监视如何找到并加载共享库,请使用环境变量LD_DEBUG 。
有许多Linux LD_ 环境变量 ,如man ld.so 。 这些变量可以在可执行文件之前分配,这样我们就可以改变程序的运行特性。 一些有用的变量包括:
LD_LIBRARY_PATH用于设置运行时库的搜索路径;
用于跟踪查找和加载共享库的LD_DEBUG ;
LD_TRACE_LOADED_OBJECTS用于显示程序所有加载的库(行为类似于ldd );
LD_PRELOAD用于在所有其他库被查找之前,在开始时强制将库注入到程序;
用于分析一个指定的共享库的LD_PROFILE和LD_PROFILE_OUTPUT 。 R用户谁已经阅读第3.4.1.1 写入R扩展 sprof应该记得,这是用来分析从R内编译的代码。
使用LD_DEBUG可以看出:
~/Desktop/dgemm$ LD_DEBUG=help cat Valid options for the LD_DEBUG environment variable are: libs display library search paths reloc display relocation processing files display progress for input file symbols display symbol table processing bindings display @R_313_4045@ion about symbol binding versions display version dependencies scopes display scope @R_313_4045@ion all all prevIoUs options combined statistics display relocation statistics unused determined unused DSOs help display this help message and exit To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable.
在这里我们特别感兴趣的是使用LD_DEBUG=libs 。 例如,
~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas 5974: find library=libblas.so.3 [0]; searching 5974: trying file=/usr/lib/R/lib/libblas.so.3 5974: trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3 5974: trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3 5974: trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3 5974: trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3 5974: trying file=/usr/lib/i386-linux-gnu/libblas.so.3 5974: trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3 5974: trying file=/usr/lib/libblas.so.3 5974: calling init: /usr/lib/libblas.so.3 5974: calling fini: /usr/lib/libblas.so.3 [0]
显示了R程序试图找到并加载libblas.so.3各种尝试。 所以如果我们可以提供我们自己的libblas.so.3版本,并确保R先找到它,那么问题就解决了。
首先在我们的OpenBLAS库libopenblas.so工作路径中创建一个符号链接libblas.so.3 ,然后展开与我们的工作路径(并导出它)的默认LD_LIBRARY_PATH :
~/Desktop/dgemm$ ln -sf libopenblas.so libblas.so.3 ~/Desktop/dgemm$ export LD_LIBRARY_PATH = $(pwd):$LD_LIBRARY_PATH ## put our working path at top
现在再来检查一下库加载过程:
~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas 6063: find library=libblas.so.3 [0]; searching 6063: trying file=/usr/lib/R/lib/libblas.so.3 6063: trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3 6063: trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3 6063: trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3 6063: trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3 6063: trying file=/usr/lib/i386-linux-gnu/libblas.so.3 6063: trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3 6063: trying file=/home/zheyuan/Desktop/dgemm/libblas.so.3 6063: calling init: /home/zheyuan/Desktop/dgemm/libblas.so.3 6063: calling fini: /home/zheyuan/Desktop/dgemm/libblas.so.3 [0]
大! 我们已经成功地骗了R.
3.用OpenBLAS进行实验
~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R GFLOPs = 8.77
现在,一切都按预期工作!
4.取消设置LD_LIBRARY_PATH (为了安全起见)
使用后取消设置LD_LIBRARY_PATH是一个好习惯。
~/Desktop/dgemm$ unset LD_LIBRARY_PATH
*********************
解决方案2:
*********************
在这里我们提供了另一种解决方案,通过利用我们解决方案1中提到的环境变量LD_PRELOAD 。 LD_PRELOAD的使用更加“残酷”,因为它会在任何其他程序之前,甚至在C库libc.so之前强制将给定的库加载到程序中。 这常常用于Linux开发中的紧急修补。
如原文的第2部分所示,共享的BLAS库libopenblas.so具有SONAME libopenblas.so.0 。 SONAME是动态库加载程序在运行时要查找的内部名称,因此我们需要使用此SONAME与libopenblas.so进行符号链接:
~/Desktop/dgemm$ ln -sf libopenblas.so libopenblas.so.0
那么我们出口它:
~/Desktop/dgemm$ export LD_PRELOAD=$(pwd)/libopenblas.so.0
请注意,即使libopenblas.so.0位于$(pwd)下, libopenblas.so.0的完整路径 libopenblas.so.0需要被提供给LD_PRELOAD才能成功加载。
现在我们启动Rscript并检查LD_DEBUG发生了什么:
~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas 4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4865: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4868: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0] 4870: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4869: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4867: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0] 4860: find library=libblas.so.3 [0]; searching 4860: trying file=/usr/lib/R/lib/libblas.so.3 4860: trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3 4860: trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3 4860: trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3 4860: trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3 4860: trying file=/usr/lib/i386-linux-gnu/libblas.so.3 4860: trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3 4860: trying file=/usr/lib/libblas.so.3 4860: calling init: /usr/lib/libblas.so.3 4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4874: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4876: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so 4860: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0] 4860: calling fini: /usr/lib/libblas.so.3 [0]
与我们在解决方案1中看到的与我们自己版本的libblas.so.3作弊的R libblas.so.3 ,我们可以看到这一点
libopenblas.so.0被加载,因此首先被Rscript找到;
找到Rscript后, Rscript继续搜索并加载libblas.so.3 。 但是, 原来的回答中解释说,这个“先来先得”的规定不起作用。
好,一切正常,所以我们测试我们的mmperf.c程序:
~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R GFLOPs = 9.62
9.62的结果比我们在前面的解决方案中看到的只有偶然的8.77更大。 作为使用OpenBLAS的测试,我们不会多次运行实验来获得更高级的结果。
然后像往常一样,我们最后取消设置环境变量:
~/Desktop/dgemm$ unset LD_PRELOAD
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。