- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者:京东科技 李永萍 。
GridGraph:Large-Scale Graph Processing on a Single Machine Using 2-Level Hierarchical Partitioning 。
图计算系统按照计算方式划分可分为:单机内存图处理系统,单机核外图处理系统,分布式内存图处理系统,分布式核外图处理系统。本文将详细介绍单机核外图处理系统GridGraph.
单机内存图处理系统受限于内存空间和单机算力,能够解决的图规模有限。分布式内存图处理系统理论上可以随着集群规模的增大进而解决更大的图规模,但集群间的网络带宽问题,负载不均衡,同步开销大,容错开销和图分割挑战也愈变明显。无论是单机还是分布式,内存式图处理系统能够处理的图规模都是有限的。因此想要使用更少的资源解决更大的图规模,可以使用单机核外图处理系统。单机核外图处理系统使用磁盘顺序读写进行数据置换,能够在有限的内存中计算更大规模的图。单机核外图处理系统在最大化利用磁盘顺序读写,在选择调度和同异步计算模式等方面做出了重要探索.
GridGraph是一种单机核外图处理系统,在大规模图处理系统中充分利用磁盘读写,在有限内存中高效完成大规模图计算.
GridGraph充分利用磁盘大容量,解决单机内存有限时实现大规模图计算问题。GridGraph采用Streaming-Apply方式减少计算中的IO 请求数量,通过文件调入顺序减少不必要的io开销。 同时GridGraph也利用顺序读和顺序写的特点,尽可能的较少硬盘的写操作.
GridGraph的主要贡献有:
1、基于边列表快速生成一种新的图表示形式--网格划分。网格划分是一种不同于邻接矩阵和邻接链表的表示形式,网格划分不需要将index排序,网格的边block可以由未排序的边列表转换而来,数据前置预处理开销小,可应用于不同的算法和不同的机器.
2、2-level hierarchical partitioning 使用两层分区划分模式,该模式不仅适用于核外,在内存中同样有效.
3、提出streaming-apply模式,以提高IO。通过双滑动窗口(Dual sliding windows)保证顶点访问的局部性.
4、提供灵活的点边流式接口函数,通过用户自定义过滤函数来跳过非活跃顶点(活跃顶点:bitmap中该顶点index的状态为1)或非活跃边的计算。对于活跃顶点集随着收敛而缩小的迭代算法,这种方法显著提高了算法的性能.
为了在有限的内存中完成大规模图计算,并严格控制内存消耗,需要将图进行网格划分.
1、顶点集划分成P个均匀的chunk.
2、边集划分在P*P个block中,行表示源顶点,列表示目的顶点.
GridGraph partition预处理方式如下:
1、主线程从原始的无序边集中读取边,读取到一批边后,将这批边数据加入队列中。(根据磁盘带宽,一般选择24M做为这批边的大小) 。
2、每个工作线程从队列中获取任务,计算边所属的block,将边加入到边block文件中。为了提高I/O吞吐量,每个工作线程维护每个block的本地缓冲区,一旦缓冲区满就刷新到文件.
分区过程结束后,GridGraph就可以进行计算了。然而,由于现实世界图的不规则结构,一些边block可能太小,无法在HDD上实现大量的连续带宽。因此,可能由于频繁的磁盘寻道,有时无法实现顺序带宽。为了避免这种性能损失,GridGraph需要一个额外的合并阶段,以便在基于HDD的系统上更好地执行,该阶段将边block文件逐个追加到一个大文件中,并在元数据中记录每个块的起始偏移量.
不同于GraphChi的shard分片模式,GridGraph不需要对边block排序,减少了IO和计算开销,我们只需要在磁盘上读写一次边,而不是在GraphChi中多次遍历边.
而对于X-Stream来说,X-Stream不需要显式的预处理。根据流分区,边被打乱到几个文件。不需要排序,分区的数量非常少。对于许多顶点数据都能装进内存的图,只需要一个流分区。然而,这种划分策略使得它在选择调度中效率低下,这在很大程度上影响了它在许多迭代算法中的性能,因为在某些迭代中只使用了一部分顶点。(GraphChi和X-Stream都是单机核外图计算系统,在此不赘述。) 。
何为选择调度?选择调度是将图数据文件(一般是边文件)划分为多个block并按顺序编号,设置一个bitmap记录所有block的访问状态,若是需要访问则将bitmap中index为block编号的状态置为1,在调度时跳过状态为0的block,选择状态为1的block从磁盘置入内存中进行计算。若是bitmap为空,则默认所有block都需要参与计算,则将block按序从磁盘置入内存。block的大小决定了选择调度的差异,block越大,包含的数据越多,block置换的概率越低,选择调度越好。反之,block越小,包含的数据越少,计算时需要置换block的概率越高,选择调度越差.
GridGraph完成预处理的时间非常短。此外,生成的网格格式可用于运行在同一图上的所有算法。通过分区,GridGraph能够进行选择性调度,减少对没有活跃边的边块的不必要访问。这在许多迭代算法(如BFS和WCC)中贡献很大,因为其中大部分顶点在许多迭代中都是不活动的.
内存(In-memory)图计算系统将全都数据读取到Memory内存中,使用到系统中的Cache(缓存)和Memory(内存)来完成图计算过程,核外(Out-of-core)图计算系统则将数据存储到Disk磁盘中,计算时再将所需数据置换到Memory(内存)中,为了缓解CPU和Memory之间的速度差异,通常会将数据存储至Cache缓存中。磁盘存储空间>内存存储空间>缓存存储空间.
那么如何选择Partition呢?
粒度越细(即P值越大),预处理时间越长,P越大,每一个chunk能表示的范围越广,那么每个block能存储的边数据越多,顶点数据的访问局部性越好,block置换概率越低, 选择性调度潜力 就越大。因此,在划分时,P越大越好。目前,我们暂时选择P的最大值,这样顶点数据可以适应 最后一级缓存 。那么P的最小值可以这样设定:
(V/P)*U<=C<=>P>=C/UV 。
其中V是图的顶点数,C是最后一级cache缓存的大小,U是每个顶点的大小。(V/P)表示chunk中可表示的顶点范围,(V/P)*U则表示每个chunk的大小,为了适应最后一级缓存,能够一次将一个chunk的所有数据放入最后一级缓存中,则chunk的大小应小于等于C,公式进行变换得到P的最小值为C/UV. 。
这种分区方式不仅表现出良好的性能(特别是在内存情况下),而且节省了很多的预处理成本.
GridGraph使用流应用处理模型,在该模型中只需要读取边一次,并且只需遍历一次顶点即可完成写I/O总量.
GridGraph提供了两个流式处理函数分别处理顶点(Algorithm1)和边(Algorithm2):
F是一个可选的用户自定义函数,它接受顶点作为输入(StreamVertices时是当前顶点,StreamEdges时是block中每一条边的源顶点),并且返回一个布尔值来指示流中是否需要该顶点。当算法需要选择性调度用于跳过一些无用的流时通常与位图一起使用,位图可以紧凑地表示活动顶点集.
Fe和Fv是用户自定义的描述流处理的函数,Fe接受一个边做为输入,Fv接受一个顶点做为输入,返回一个R类型的值,返回值被累加,并作为最终结果提供给用户。该值通常用于获取活跃顶点的数量,但不限于此用法,例如,用户可以使用这个函数来获得PageRank中迭代之间的差异之和,以决定是否停止计算.
GridGraph将顶点数据存储在磁盘上。使用内存映射机制(将顶点数据文件通过mmap内存映射机制映射到内存中)来引用文件中的顶点数据,每个顶点数据文件对应一个顶点数据数组。因此访问顶点数据文件就像访问内存中的数组一样,并简化了编程模型:开发人员可以将其视为普通数组,就像它们在内存中一样.
以PageRank为例,我们来看看GridGraph是如何实现算法的.
PageRank是一种链接分析算法(Algorithm3),计算图中每个顶点的数值权重,以测量其在顶点之间的相对重要性。初始所有顶点的PR值都是1,在每次迭代中,每个顶点向邻居发送自己的贡献,即当前PR值除以它的出度。每个顶点将从邻居收集到的贡献进行汇总,并将其设置为新的PR值。当均值差达到某个阈值时,算法收敛.
GridGraph流式读取每个block的边,当block在第i行第j列时,和这个block相关的顶点数据也落在第i行第j列的chunk中,每个block都包含两个顶点chunk,source chunk(源顶点chunk)和destination chunk(目的顶点chunk).
通过P的设定,使得block足够小,能够将一个block放入最后一级缓存中,这样在访问与block相关的顶点数据时,可以确保良好的局部性.
根据更新模式,block的访问顺序可以是面向行或面向列的。假设顶点状态从源顶点传播到目标顶点(这是许多应用程序中的典型模式),即源顶点数据被读取,目标顶点数据被写入。由于每个边block的列对应于目标顶点块,需要对目标顶点块进行写操作,在这种情况下优先采用面向列的访问顺序。当目的顶点所在block被缓存在内存中时,GridGraph从上到下流向同一列中的block,因此昂贵的磁盘写操作被聚合和最小化。特别是对于SSD系统来说,这是一个非常重要的性能,写入大量数据写性能会相应下降。另一方面,由于SSD有写入周期的上限,因此尽可能减少磁盘随机写入以实现理想的持久性是很重要的.
以PageRank为例,我们来看看GridGraph是如何使用双滑动窗口对顶点信息进行更新。读窗口(从源顶点数据中读取当前顶点的PageRank值和出度)和写窗口(对目标顶点的新PageRank值的贡献进行累加)作为GridGraph流沿block以面向列的顺序滑动.
1、初始化,每个顶点初始的PR值都为1 。
2、Stream edge block(1,1),此时src.chunk 1和dest.chunk 1都加载进内存中 。
读窗口:读取src.chunk 1的PR和Deg 。
写窗口:写dest.chunk 1的NewPR 。
IO总量:读取block中2条边,读取src.chunk 1中的顶点(1,2),读取dest.chunk 1中的顶点(1,2) 。
3、Stream edge block (2,1),此时dest.chunk 1在内存中,将src.chunk 2也加载进内存中 。
读窗口:读取src.chunk 2的PR和Deg 。
写窗口:写dest.chunk 1的NewPR 。
IO总量:读取block中2条边,读取src.chunk 2中的顶点(3,4) 。
4、Stream edge block (1,2),dest.chunk 1已经全部更新完成,将更新后的dest.chunk1写回磁盘种,将src.chunk 1和dest.chunk 2加载进内存中 。
读窗口:读取src.chunk 1的PR和Deg 。
写窗口:写dest.chunk 2的NewPR 。
IO总量:读取block中2条边,将dest.chunk 1中的顶点(1,2)的结果写入磁盘,读取src.chunk 1中的顶点(1,2),读取dest.chunk 2中的顶点(3,4) 。
5、Stream edge block (2,2),此时dest.chunk 2在内存中,将src.chunk 2也加载进内存中 。
读窗口:读取src.chunk 2的PR和Deg 。
写窗口:写dest.chunk 2的NewPR 。
IO总量:读取block中1条边,读取src.chunk 2中的顶点(3,4) 。
6、完成dest所有chunk的遍历,将dest.chunk 2更新后的结果写入磁盘中.
IO总量:将dest.chunk 2中的顶点(3,4)的结果写入磁盘中.
在上面的一次流应用迭代中给出了网格图的I/O分析,其中所有的边和顶点都被访问。以面向列的顺序访问边block为例:所有边被访问一次,源顶点数据被读取P次,而目标顶点数据被读写一次。在一次完整迭代并收敛中使用的IO:
E+(2+P)*V 。
E:表示读取所有边 。
2:读取和写入目标顶点的数据 。
P:读取每个P中源顶点数据 。
通过对边的只读访问,GridGraph所需的内存非常紧凑。事实上,它只需要一个小的缓冲区来保存正在Stream的边blocl,以便页缓存可以使用其他空闲内存来保存更多的边block,当活跃边block变得足够小以适合内存时,这是非常有用的。这种Streaming-Apply-Processing-Model流式应用模型的另一个优点是它不仅支持经典的BSP模型,而且还允许异步更新。由于顶点更新是即时的,更新的效果可以通过跟踪顶点的遍历来获得,这使得许多迭代算法收敛得更快。由此可看出:P应该是使顶点数据放入内存的最小值。因此,更小的P应该是最小化I/O量的首选,这似乎与上面我们所说P越大越好,更大的网格分区原则相反.
前面我们已经解释过什么是选择调度,即跳过不活跃的边block。在Stream函数中的由F传入位图,由此跳过不活跃的边block.
P越小,粒度越粗,访问顶点的次数更少,更差的局部性,选择调度更差 。
P越大,粒度越细,更好的局部性,选择调度更好,访问顶点的次数更多 。
为了解决这个难题,在边网格上应用了二级分区,以减少顶点的I/O访问.
在P*P的网格中再进行一层网格划分,第二层网格有Q*Q个边网格。将Q*Q的分区应用在P*P的网格中.
Q的选择应满足:
(V/Q)*U <= M 。
M是给定的内存容量.
在前面我们提到,P的选择是为了将顶点数据放入容量远小于内存的上一级缓存中,因此P应该远大于Q.
整个网格被分成4个大块,每个大块包含4个小块。每个块内的数字表示访问顺序。在原始的4×4分区中使用了精确的面向列的访问顺序。在应用了二级分区后,P:2×2 变成 Q:4×4分区之后,我们以面向列的顺序访问粗粒度(大)块,在每个大块中,我们访问细粒度的块(小)块以列为导向的顺序。这种2级分层分区不仅提供了灵活性,而且还提高了效率,因为高级分区(第二级分区)是虚拟分区,GridGraph能够利用较低级别分区(第一级分区)的结果,因此不会增加更多的实际开销。并且可以使用P网格划分的结果进行选择调度.
GridGraph定义了一种新的图表示形式:网格划分,用于适应有限的内存;使用双窗口模式减少IO访问的总量,特别是写IO;使用选择调度减少掉无用的IO;使用2级分区划分方式保证了P尽可能大的同时减少IO访问。GridGraph在有限的内存中,并提高IO效率,高效的完成了核外图计算过程.
源码地址: https://github.com/thu-pacman/GridGraph 。
将原始二进制文件处理成grid格式的block文件 。
我们来看看block文件是如何划分处理的:
从input文件中遍历读取IOSIZE的数据放入buffers[cursor]中,tasks记录当前当前游标的字节数<cursor, bytes>,在threads中获取tasks中的cursor和bytes,根据cursor读取buffers中的数据,将buffers[cursor]中的数据根据src和dst所属的partition,放入local_buffer[i][j]中,将local_buffer[i][j]的数据分别写入block[i][j]文件中。如下图所示:
代码位于:tools/preprocess.cpp 。
1、打开文件读取数据,将数据加入task处理,在这里,buffers的定义是全局的,tasks保存cursor和buffers数据大小.
2、那么我们来看看tasks是什么,tasks是一个队列,保存当前游标和数据大小。grid_buffer_size = 12*8*8,12表示<4 byte source, 4 byte destination, 4 byte float typed weight>,8*8表示每次读取到64byte的数据时写一次磁盘,是个magic number.
3、真正进行数据处理的是threads的任务。每个thread处理一个buffers[cursor]的数据.
将local_buffer的数据写入对应的block文件中 。
4、生成column文件,将所有block文件按照列遍历方式保存到column文件中,并将每个block文件的大小保存至column_offset文件中.
5、同理生成row文件,按照行遍历方式读取block文件写入row文件中,并记录offset.
6、最后将处理好的数据信息(是否含有权重,顶点数,边数,partition数)写入meta文件中.
执行grid代码后,会生成P*P个block文件,一个column文件、row文件、column_offset、row_offset及meta文件.
代码位于:core/graph.hpp 。
空间初始化,并读取meta信息和column_offset、row_offset的数据,并记录每个block文件大小 。
如果bitmap为空,并且顶点数据字节总数(顶点数据字节总数初始化为0,可在算法实现时设置,一般为顶点总数 顶点大小)大于0.8 内存字节数,先获取partitions的begin_vid和end_vid,再遍历每一个partition,每个partition中的每个vertex按照process执行,将返回值求和相加。最后等待所有partition执行结束,得到begin_vid和end_vid.
如果bitmap不为空或者顶点数据字节总数小于等于0.8*内存字节数,则遍历每一个partition,获取每个partition的begin_vid和end_vid。如果bitmap为空,则遍历partition中的所有顶点,按照process执行,返回值相加。否则,从begin_vid开始,按照bitmap遍历,bitmap为1的vid执行process,返回值相加.
根据bitmap决定需要遍历的partition,如果bitmap为空,则所有partition都要遍历,bitmap不为空根据partition中是否包含bitmap中的vid,包含则该partition需要遍历.
统计所有需要遍历的partition的文件总大小 。
默认update_mode=1,若update_mode=0则为行更新模式(行主序更新),update_mode=1则为列更新模式(列主序)。数据准备阶段:
遍历需要访问的分区,分区访问方式为:列不变,行从小到大进行遍历,行遍历完后列再向右移。每次读取分区中IOSIZE大小的数据,最后不够IOSIZE则读取PAGESIZE大小的数据 。
每条边按照process的方法执行操作 。
若是行主序,实现则如下:按照行遍历方式读取需要遍历的partition,每次处理IOSIZE大小的数据 。
数据处理方式则是读取row文件,从offset开始读取length的数据放入buffer中,然后遍历每一条边,每条边按照process执行.
下面我们来看看实际使用,以PageRank算法实现为例,这里不再详述PageRank算法原理.
代码位于:example/pagerank.cpp 。
先初始化每个顶点的degree:在这里update_mode=0,使用行主序更新.
初始化每个顶点的pr值为1:
遍历每一条边更新计算每条边的贡献值:
更新每个顶点上的pr值,最后一轮迭代则直接计算并更新sum:
在grid文件处理中,有几个可优化的点:
1)、在读取输入文件时,可以根据文件个数并行读取文件,加快文件处理速度.
2)、初始化grid空间,因为初始化时每个block互不影响,可以使用omp并行初始化提高效率.
3)、thread线程中,因为每个线程处理的是不同的cursor的buffers数据,每个thread生成自己的local_buffer写入block文件,因为threads中没有数据交互,因此也可以并行化.
在stream_vertices和stream_edges我们都进行了分析,可以看出,不论是行主序还是列主序,都免不了折线式(Z型)的边block遍历策略,其可优化的点如下:
1、可将Z型边遍历可更改一下,改成U形遍历,以列主序为例,当遍历到最后一行的src时,src不变保持在内存中,此时dst向右移,src从下往上遍历,以此类推,可节省P次的页面置换.
GridGraph提供一种在有限内存中完成大规模图计算系统。解决单机内存或分布式内存无法解决的大规模图计算问题。提供一种新式的切图方式,将顶点和边分别划分为1D chunk和2D block来表示大规模图的网格表示;使用一种新的streaming-apply模型,提高IO,对顶点的局部性友好的方式流化读取边block;GridGraph能够在不涉及I/O访问的情况下访问内存中的顶点数据,并且跳过不需要遍历的边block,提高算法执行效率.
GridGraph将顶点划分为P个顶点数量相等的chunk,将边放置在以P*P的网格中的每一个block中,边源顶点所在的chunk决定其在网格中的行,边目的顶点所在的chunk决定其在网格中的列。它对Cache/RAM/Disk进行了两层级的网格划分,采用了Stream vertices and edges的图编程模型。计算过程中的双滑动窗口(Dual Sliding Windows)也大大减少了I/O开销,特别是写开销。以block为单位进行选择调度,使用原子操作保证线程安全的方式更新顶点。论文中提到在边网格上采用压缩技术,以进一步降低所需的I/O带宽,提高效率.
参考文献:
1. Xiaowei Zhu, Wentao Han and Wenguang Chen. GridGraph: Large-Scale Graph Processing on a Single Machine Using 2-Level Hierarchical Partitioning . Proceedings of the 2015 USENIX Annual Technical Conference, pages 375-386. 。
2. ZHU Xiaowei — GridGraph: Large-‐Scale Graph Processing on a Single Machine. Using 2-‐Level Hierarchical Parffoning. Xiaowei ZHU, Wentao HAN, Wenguang CHEN . Presented at USENIX ATC '15 。
3. Amitabha Roy, Ivo Mihailovic, Willy Zwaenepoel. X-Stream: Edge-centric Graph Processing using Streaming Partitions 。
4. Aapo Kyrola Carnegie Mellon University akyrola@cs.cmu.edu, Guy Blelloch Carnegie Mellon University guyb@cs.cmu.edu,Carlos Guestrin University of Washington guestrin@cs.washington.edu. GraphChi: Large-Scale Graph Computation on Just a PC 。
最后此篇关于图计算引擎分析--GridGraph的文章就讲到这里了,如果你想了解更多关于图计算引擎分析--GridGraph的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我想填充 3D 等高线图 (contour3(X,Y,Z)),就像 2D 等高线填充图 (contourf(X,Y,Z))。但我无法弄清楚如何实现这一目标。 contour3 和 surf 的组合不是
我有一个 c3.js 折线图,表示 2 个值的演变。我需要折线图的工具提示是饼图(工具提示 = 另一个 c3.js 图形)。 这是我成功的: http://jsfiddle.net/owhxgaqm/
我有具有结构的 Pandas 数据框: A B 0 1 1 1 2 1 2 3 4 3 3 7 4 6 8 如何生成 Seaborn Violin 图,每列作为其自己的单独
我正在使用 D3DXSPRITE 方法将我的 map 图 block 绘制到屏幕上,我刚刚添加了一个缩放功能,当您按住向上箭头时会放大,但注意到您现在可以看到图 block 之间的间隙,这是一些屏幕截
今天我们开始学习目前学习到的最难最复杂的数据结构图。 简单回顾一下之前学习的数据结构,数组、单链表、队列等线性表中数据元素是一对一关系,而树结构中数据元素是一对多关系,而图结构中数据元素则是多对
1、系统环境如下图: 2、为该系统添加一块新的虚拟硬盘,添加后需重启虚拟机,否则系统不识别;如下图,/dev/sdc 是新添加的硬盘; 3、fdisk /dev/sdc为新硬盘创建分区:
1、nagios简介 nagios是一款开源的电脑系统和网络监视工具,能有效监控windows、linux和unix的主机状态,交换机路由器等网络设置,打印机等。在系统或服务状态异常时发
越来越多人开始习惯用手机上网,浏览网页、查看邮件···移动化已经成为互联网发展必然趋势,包括facebook在内的很多互联网公司都将移动广告作为下一个淘金地
1.图片处理 1.圆角图片 复制代码 代码如下: /** * 转换成圆角 * &n
Microsoft SQL Server Management Studio是SQL SERVER的客户端工具,相信大家都知道。我不知道大伙使用导入数据的情况怎么样,反正我最近是遇到过。主要是因为没
debian6系统: 首先先安装mysql吧: 打开终端(root)用户登入 apt-get purge mysql-server-5.5 安装完成后: 默认情况下Mysql只允许本地登录
fedora16英文环境下支持中文输入法的方法 fedora16英文环境下支持FCITX的中文输入法: $ im-chooser 就会出现选择界面,选择第二个就行了。
Net预编译命令 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -? 显示说明 我们需要选择的命令为&n
有的时候电脑出现一些故障有的时候通过将其修改bios设置的方法来解决故障,那么在bios上设置能不能将电脑恢复出厂设置呢?其实也是可以的。方法也很简单的,只要会进入电脑的bios懂的上面英文的意思就
笔者曾介绍过Deepin 将对龙芯进行全面支持,打造最优美龙芯电脑桌面。现在Deepin团队移植工作取得了突破性的成果,Deepin桌面已经在龙芯3A和龙芯3B电脑上成功运行起来了。 以下为龙芯3
在安装一些软件之后,我们的电脑总是会发生一点小变化,不是桌面上多了几个网址图标,就是IE浏览器的默认主页被篡改成乱七八糟的网址。最可气的是,在IE设置中将默认主页改回来后,下次启动Win7后又变了回
“注册表编辑器怎么打开”虽说不是很难的问题,但是对于对电脑常识不是很擅长的网民来说,当电脑出现问题或需要更改设置时,着实还是件头疼的问题。因为需要打开注册表进行操作解决。那么如何打开注册表编辑器呢?
这篇文章重点介绍10个重要的WordPress安全插件和技巧,用来保护WordPress网站或者博客。 1. WP Security 人工帮助你修复被黑客入侵的网站,只要按照他们网站上的联系电话
其实运用object和javascript调用外部文件,也能实现不同栏目调用不同友情链接,即相当于调用不同栏目友情链接文件, {dede:field.typeid/}来获取当前栏目的ID。
我有一个复值矩阵。 如果我发出命令: plot(myMatrix) 然后它在图形设备上显示一种散点图,X 轴标记为 Re(myMatrix),Y 轴标记为 Im(myMatrix)。这显示了我正在寻找
我是一名优秀的程序员,十分优秀!