【问题标题】:Improvement of performance for a maze solving program in Python用 Python 改进迷宫求解程序的性能
【发布时间】:2018-06-29 11:07:01
【问题描述】:

各位程序员。我的一个项目需要帮助。我正在制作一个迷宫解决程序。它读取一个图像文件,该文件必须是黑白的(黑色像素是墙壁,白色像素是路径),顶部只有一个像素是迷宫的入口,底部只有一个白色像素是出口。

代码分为三个主要部分:

1) 程序首先在迷宫中创建节点,遵守一组规则。例如这里是一个简单的迷宫:

这里所有的节点都用红色绘制:

节点就像拐角,十字路口,每个可以改变方向的点。还测量了每个节点与迷宫出口之间的距离。当它生成所有节点时,它会将它们放在一个列表中。

2) 一旦所有节点生成,程序将遍历列表中的所有节点,并尝试在每个可能的方向搜索其他节点,以“链接”它们,建立可能的路径。例如,如果它检测到某个节点上方有一条路径,它将从该节点的坐标开始搜索一行中的每个像素,然后向上,再次遍历节点列表的所有节点,以查看是否有另一个节点匹配这些坐标。如果它在某个时候找到一个,它会将它们链接起来,然后开始向右搜索(当然,如果有一条通往右边的路径),等等。

3) 一旦所有的节点都连接在一起并建立了每条可能的路径,它将从迷宫的入口节点开始,并运行我的 A* 算法实现以找出正确的路径,如果出现则返回这是一个死胡同。如您所见,它毫无困难地解决了迷宫。

所以我的程序有效。那有什么问题呢? 问题是节点链接部分。在小型迷宫中,大约需要半秒钟。但是把迷宫弄大一点,那么节点的数量就会急剧增加。而且由于它遍历节点列表很多时间(它搜索每个节点的每个像素一次),你可以想象如果我有 600 000 个节点......这将需要很长时间。

这就是我寻求帮助的目的:一种将节点链接在一起的更好、更快的方法。 我已经在 pastebin 上发布了整个代码(https://pastebin.com/xtmTm7wb,如果有点乱,很抱歉,我编程时已经很晚了)。节点链接部分从第 133 行开始,到第 196 行结束。

这是节点链接代码:

counter = 0
last = 0
for node in nodelist:
    a = node.pos[0]
    b = node.pos[1]
    if node.paths[0]:
        for i in range(b-1,0,-1):
            if mazepix[a,i] == blackpix:
                break
            if any(x.pos == (a,i) for x in nodelist):
                for iteration in nodelist:
                    if iteration.pos == (a,i):
                        indexofnodetolinkto = iteration.index
                        break
                node.connections.append(indexofnodetolinkto)
                # print("Linking node %d and node %d..."%(node.index, indexofnodetolinkto))
                break

    if node.paths[1]:
        for i in range(a+1,maze.size[0]):
            if mazepix[i,b] == blackpix:
                break
            if any(x.pos == (i,b) for x in nodelist):
                for iteration in nodelist:
                    if iteration.pos == (i,b):
                        indexofnodetolinkto = iteration.index
                        break
                node.connections.append(indexofnodetolinkto)
                # print("Linking node %d and node %d..."%(node.index, indexofnodetolinkto))
                break

    if node.paths[2]:
        for i in range(b+1,maze.size[1]):
            if mazepix[a,i] == blackpix:
                break
            if any(x.pos == (a,i) for x in nodelist):
                for iteration in nodelist:
                    if iteration.pos == (a,i):
                        indexofnodetolinkto = iteration.index
                        break
                node.connections.append(indexofnodetolinkto)
                # print("Linking node %d and node %d..."%(node.index, indexofnodetolinkto))
                break

    if node.paths[3]:
        for i in range(a-1,0,-1):
            if mazepix[i,b] == blackpix:
                break
            if any(x.pos == (i,b) for x in nodelist):
                for iteration in nodelist:
                    if iteration.pos == (i,b):
                        indexofnodetolinkto = iteration.index
                        break
                node.connections.append(indexofnodetolinkto)
                # print("Linking node %d and node %d..."%(node.index, indexofnodetolinkto))
                break

    counter += 1
    percent = (counter/nbrofnodes)*100
    if int(percent)%10 == 0 and int(percent) != last:
        print("Linking %d%% done..."%percent)
        last = int(percent)

print("All node linked.")

感谢您阅读所有这些内容,我知道这不是一个非常精确的问题,但是我花了很多时间来尝试完成这项工作,现在我真的坚持我可以改进的方式它^^'。

【问题讨论】:

标签: python algorithm python-imaging-library a-star maze


【解决方案1】:

你的程序超级慢,因为这部分需要很长时间,而且你对每个节点都做了很多次:

            for iteration in nodelist:
                if iteration.pos == (i,b):
                    indexofnodetolinkto = iteration.index
                    break

有很多方法可以让它更快。

您可以使用位置作为键将节点放入字典中,因此您只需查找位置即可找到那里的节点。

更好的是,您可以将节点放入行列表和列列表中,按位置排序,然后只尝试连接列表中的相邻节点。

但最好的办法是完全忘记这些节点,直接在位图上进行 BFS 搜索。

由于这是一个有趣的问题,我用简单的 BFS 编写了一个快速版本。我不想破坏你所有的乐趣,所以这里只是 BFS 部分,这样你就可以看到我在图像上直接做 BFS 的意思:

#Breadth-first search over the graph
#We use special colored pixels in the image to mark visited locations and their distance
nextlevel=[(entrypos,0)]
nextdist=0
mazepix[entrypos,0] = markerPixel(nextdist)
exitpos = -1
while exitpos<0:
    if len(nextlevel) < 1:
        print("Could not find exit!")
        return
    prevlevel = nextlevel
    nextdist += 1
    nextlevel = []
    nextpix = markerPixel(nextdist)

    for prevpos in prevlevel:
        for dir in [(-1,0),(0,1),(1,0),(0,-1)]:
            x = prevpos[0]+dir[0]
            y = prevpos[1]+dir[1]
            if x>=0 and y>=0 and x<W and y<H and mazepix[x,y]==whitepix:
                nextlevel.append((x,y))
                #mark it used and distance mod 3
                mazepix[x,y]=nextpix
                if y>=H-1:
                    exitpos=x

我们没有使用带有对象和链接的单独集合来记住路径,而是将像素标记为直接在图像中访问过。我们无需使用任何类型的实际链接将一个像素链接到另一个像素,而是在需要时检查所有 4 个方向以寻找相邻的白色像素。

这会进行逐级 BFS,因此我们始终知道新像素与入口的距离,我们标记访问像素的颜色表示其与入口的距离(模式 3)。这使我们能够在找到出口时重建最短路径。

编辑:已经很久了,OP 玩得很开心,所以这里是完整的 python 求解器:

from PIL import Image
import math
import sys
import time
import pickle
import os

whitepix = (255,255,255)
blackpix = (0,0,0)
redpix = (255,0,0)
greenpix = (0,255,0)

def markerPixel(distance):
    val=120+(distance%3)*40
    return (val,val,0)

def smallerMarker(pixel):
    return markerPixel(pixel[0]-1)

def isMarker(pixel):
    return pixel[2]==0 and pixel[0]==pixel[1] and pixel[0]>=120

def solve(imagename, outputname, showmarkers):

    maze = Image.open(imagename)
    maze = maze.convert('RGB')
    mazepix = maze.load()
    nodelist = []

    print(maze.size)

    starttime = time.time()

    W = maze.size[0]
    H = maze.size[1]
    entrypos = -1

    # Find the entry
    for i in range(0,W):
        if mazepix[i, 0] == whitepix:
            entrypos=i
            break

    if entrypos < 0:
        print("No entry!")
        return

    #Breadth-first search over the graph
    #We use special colored pixels in the image to mark visited locations and their distance
    nextlevel=[(entrypos,0)]
    nextdist=0
    mazepix[entrypos,0] = markerPixel(nextdist)
    exitpos = -1
    while exitpos<0:
        if len(nextlevel) < 1:
            print("Could not find exit!")
            return
        prevlevel = nextlevel
        nextdist += 1
        nextlevel = []
        nextpix = markerPixel(nextdist)

        for prevpos in prevlevel:
            for dir in [(-1,0),(0,1),(1,0),(0,-1)]:
                x = prevpos[0]+dir[0]
                y = prevpos[1]+dir[1]
                if x>=0 and y>=0 and x<W and y<H and mazepix[x,y]==whitepix:
                    nextlevel.append((x,y))
                    #mark it used and distance mod 3
                    mazepix[x,y]=nextpix
                    if y>=H-1:
                        exitpos=x

    #found the exit -- color the path green
    nextpos = (exitpos,H-1)
    while nextpos != None:
        nextpix = smallerMarker(mazepix[nextpos[0],nextpos[1]])
        prevpos = nextpos
        mazepix[nextpos[0],nextpos[1]] = greenpix
        nextpos = None
        #find the next closest position -- all adjacent positions are either
        #1 closer, 1 farther, or the same distance, so distance%3 is enough
        #to distinguish them
        for dir in [(-1,0),(0,1),(1,0),(0,-1)]:
            x = prevpos[0]+dir[0]
            y = prevpos[1]+dir[1]
            if x>=0 and y>=0 and x<W and y<H and mazepix[x,y]==nextpix:
                nextpos=(x,y)
                break

    #Erase the marker pixels if desired
    if not showmarkers:
        for y in range(0,H):
            for x in range(0,W):
                if isMarker(mazepix[x,y]):
                    mazepix[x,y]=whitepix

    maze.save(outputname)

solve("maze.gif", "solved.png", False)

【讨论】:

    【解决方案2】:

    你的迷宫只有 301x301 像素,所以我认为 0.5 秒对于解决方案来说时间太长了。当我使用光栅 A* 方法时:

    整个解决方案只使用了~1.873ms,这与您的~500ms 有很大不同。粗略图 A* 的开销更大,所以我很好奇并想测试它,所以我编码了我的版本(在 C++ 中基于与上面链接中相同的代码),结果是图获取从图像到~3ms,图A* 到~0.18ms,所以与你的差异仍然很大(+/- 计算机设置差异)。

    那么首先要检查什么?

    我不是 python 编码器,但我在您的代码中看不到任何图像访问。您应该检查您的图像访问是否快速。这是新手使用类似

    的东西的常见错误
    GetPixel/PutPixel
    Pixels[][]
    

    这些通常非常缓慢(根据我在 GDI Win32 上的经验,比直接像素访问慢 1000-10000 倍)并且如果得到纠正会产生巨大的差异。欲了解更多信息,请参阅:

    列表使用的另一个常见错误是在没有预分配的情况下将元素增量添加到列表中。对于小列表,这不是问题,但是对于大量元素,在添加元素的情况下重新分配会通过一遍又一遍地复制内容来减慢速度。在列表中插入和删除元素也是如此。改进列表访问会产生巨大的影响,尤其是对于像 O(n^2) 和更慢的多项式复杂性...

    算法

    算法的微小变化可以产生巨大的影响。在您的情况下,我使用了 DIP 边缘检测技术和加速结构的拓扑排序边缘的组合。这会将O(n)O(n^2) 搜索更改为简单的O(1) 操作。这个想法是按xyyx 排序迷宫的所有顶点的有序列表。如果每个顶点都知道它在这种结构中的索引,它可以很容易地获得它的邻居顶点......

    堆栈/堆垃圾处理

    这大大减慢了速度。尤其是递归函数。递归级别和操作数大小越大,通过的效果越差。

    这是我基于上面链接的简单 C++ 示例

    //---------------------------------------------------------------------------
    //--- A star class ver: 1.00 ------------------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _A_star_h
    #define _A_star_h
    //---------------------------------------------------------------------------
    #include "list.h"
    //---------------------------------------------------------------------------
    class A_star_graph
        {
    public:
        // variables
        struct _pnt
            {
            int x,y;            // 2D position (image)
            int mx;             // mxy[y][mx] index
            int my;             // mxy[x][my] index
            int pN,pS,pE,pW;    // index of linked point in direction or -1
            int lN,lS,lE,lW;    // distance to linked point in direction or 0 (cost for A*)
            int a;              // value for A*
            _pnt()  {}
            _pnt(_pnt& a)   { *this=a; }
            ~_pnt() {}
            _pnt* operator = (const _pnt *a) { *this=*a; return this; }
            //_pnt* operator = (const _pnt &a) { ...copy... return this; }
            };
        List<_pnt> pnt;         // list of all vertexes
        List< List<int> > mxy,myx;  // xy and yx index sorted pnt[] (indexes of points)
        List<int>  path;        // found path (indexes of points)
    
        int xs,ys;              // map esolution
        DWORD col_space;        // colors for rendering
        DWORD col_wall ;
        DWORD col_path ;
    
        // internals
        A_star_graph();
        A_star_graph(A_star_graph& a)   { *this=a; }
        ~A_star_graph(){}
        A_star_graph* operator = (const A_star_graph *a) { *this=*a; return this; }
        //A_star_graph* operator = (const A_star_graph &a) { ...copy... return this; }
    
        // inteface
        void reset();                                       // clear all
        void ld(Graphics::TBitmap *bmp,DWORD col_wall);     // create graph from bitmap col_wall is 0x00RRGGBB
        void draw(Graphics::TBitmap *bmp);                  // draw map to bitmap for debuging
        void compute(int p0,int p1);                        // compute path from pnt[p0] to pnt[p1] into path[]
        };
    //---------------------------------------------------------------------------
    A_star_graph::A_star_graph()
        {           //BBGGRR
        col_space=0x00FFFFFF;
        col_wall =0x00000000;
        col_path =0x00FFAA40;
        reset();
        }
    //---------------------------------------------------------------------------
    void A_star_graph::reset()
        {
        int x,y; xs=0; ys=0; pnt.num=0; path.num=0;
        for (x=0;x<mxy.num;x++) mxy[x].num=0; mxy.num=0;
        for (y=0;y<myx.num;y++) myx[y].num=0; myx.num=0;
        }
    //---------------------------------------------------------------------------
    void A_star_graph::ld(Graphics::TBitmap *bmp,DWORD col_wall)
        {
        _pnt p,*pp,*qq;
        int i,j,x,y,c[10]={0,0,0,0,0,0,0,0,0,0};
        DWORD *p0,*p1,*p2;
        reset();
        xs=bmp->Width;
        ys=bmp->Height;
        mxy.allocate(xs); mxy.num=xs; for (x=0;x<xs;x++) mxy[x].num=0;
        myx.allocate(ys); myx.num=ys; for (y=0;y<ys;y++) myx[y].num=0;
        if (!ys) return;
        p.pN=-1; p.pS=-1; p.pE=-1; p.pW=-1; p.mx=-1; p.my=-1;
        p0=NULL; p1=NULL; p2=(DWORD*)bmp->ScanLine[0];
        for (p.y=0;p.y<ys;p.y++)
            {
            p0=p1; p1=p2; p2=NULL;
            if (p.y+1<ys) p2=(DWORD*)bmp->ScanLine[p.y+1];
            for (p.x=0;p.x<xs;p.x++)
             if ((p1[p.x]&0x00FFFFFF)!=col_wall)    // ignore wall pixels
                {
                // init connection info
                p.lN=0; p.lS=0; p.lE=0; p.lW=0;
                // init c[] array with not a wall predicates for 4-neighbors
                c[2]=0; c[4]=0; c[5]=1; c[6]=0; c[8]=0;
                if (p0) if ((p0[p.x]&0x00FFFFFF)!=col_wall) c[8]=1;
                if (p2) if ((p2[p.x]&0x00FFFFFF)!=col_wall) c[2]=1;
                if (p1)
                    {
                    if (p.x-1> 0) if ((p1[p.x-1]&0x00FFFFFF)!=col_wall) c[4]=1;
                    if (p.x+1<xs) if ((p1[p.x+1]&0x00FFFFFF)!=col_wall) c[6]=1;
                    }
                // detect vertex and its connection
                i=0;
                if (( c[2])&&(!c[8])){ i=1; p.lS=1; }   // L
                if ((!c[2])&&( c[8])){ i=1; p.lN=1; }
                if (( c[4])&&(!c[6])){ i=1; p.lW=1; }
                if ((!c[4])&&( c[6])){ i=1; p.lE=1; }
                j=c[2]+c[4]+c[6]+c[8];
                if (j==3)               // T
                    {
                    i=1; p.lN=1; p.lS=1; p.lW=1; p.lE=1;
                    if (!c[2]) p.lS=0;
                    if (!c[8]) p.lN=0;
                    if (!c[6]) p.lE=0;
                    if (!c[4]) p.lW=0;
                    }
                if (j==4)               // +
                    {
                    i=1; p.lN=1; p.lS=1; p.lW=1; p.lE=1;
                    }
                // add point
                if (i)
                    {
                    p.mx=myx[p.y].num;
                    p.my=mxy[p.x].num;
                    mxy[p.x].add(pnt.num);
                    myx[p.y].add(pnt.num);
                    pnt.add(p);
                    }
                }
            }
        // find connection between points
        for (pp=pnt.dat,i=0;i<pnt.num;i++,pp++)
            {
            if (pp->lE)
                {
                j=myx[pp->y][pp->mx+1]; qq=pnt.dat+j; pp->pE=j; qq->pW=i;
                j=abs(qq->x-pp->x)+abs(qq->y-pp->y);  pp->lE=j; qq->lW=j;
                }
            if (pp->lS)
                {
                j=mxy[pp->x][pp->my+1]; qq=pnt.dat+j; pp->pS=j; qq->pN=i;
                j=abs(qq->x-pp->x)+abs(qq->y-pp->y);  pp->lS=j; qq->lN=j;
                }
            }
        }
    //---------------------------------------------------------------------------
    void A_star_graph::draw(Graphics::TBitmap *bmp)
        {
        int i;
        _pnt *p0,*p1;
        // init
        bmp->SetSize(xs,ys);
        // clear (walls)
        bmp->Canvas->Pen->Color=TColor(col_wall);
        bmp->Canvas->Brush->Color=TColor(col_wall);
        bmp->Canvas->FillRect(TRect(0,0,xs,ys));
        // space
        bmp->Canvas->Pen->Color=TColor(col_space);
        for (p0=pnt.dat,i=0;i<pnt.num;i++,p0++)
            {
            if (p0->pN>=0){ p1=pnt.dat+p0->pN; bmp->Canvas->MoveTo(p0->x,p0->y); bmp->Canvas->LineTo(p1->x,p1->y); }
            if (p0->pS>=0){ p1=pnt.dat+p0->pS; bmp->Canvas->MoveTo(p0->x,p0->y); bmp->Canvas->LineTo(p1->x,p1->y); }
            if (p0->pE>=0){ p1=pnt.dat+p0->pE; bmp->Canvas->MoveTo(p0->x,p0->y); bmp->Canvas->LineTo(p1->x,p1->y); }
            if (p0->pW>=0){ p1=pnt.dat+p0->pW; bmp->Canvas->MoveTo(p0->x,p0->y); bmp->Canvas->LineTo(p1->x,p1->y); }
            }
        // found path
        bmp->Canvas->Pen->Color=TColor(col_path);
        for (i=0;i<path.num;i++)
            {
            p0=pnt.dat+path.dat[i];
            if (!i) bmp->Canvas->MoveTo(p0->x,p0->y);
             else   bmp->Canvas->LineTo(p0->x,p0->y);
            }
        }
    //---------------------------------------------------------------------------
    void A_star_graph::compute(int p0,int p1)
        {
        _pnt *pp,*qq;
        int i,a,e;
        List<int> upd;  // list of vertexes to update
        // init
        path.num=0;
        if ((p0<0)||(p0>=pnt.num)) return;
        if ((p1<0)||(p1>=pnt.num)) return;
        // clear with max value
        for (pp=pnt.dat,i=0;i<pnt.num;i++,pp++) pp->a=0x7FFFFFFF;
        // init A* to fill from p1
        upd.allocate(xs+ys); upd.num=0;             // preallocate
        upd.add(p1); pnt[p1].a=0;                   // start from p1
        // iterative A* filling
        for (e=1;(e)&&(upd.num);)                   // loop until hit the start p0 or dead end
            {
            // process/remove last pnt in que
            pp=pnt.dat+upd[upd.num-1]; upd.num--;
            // link exist?                     compute cost    if less        update it                   reached p0?
            i=pp->pN; if (i>=0){ qq=pnt.dat+i; a=pp->a+pp->lN; if (qq->a>a) { qq->a=a; upd.add(i); } if (i==p0) { e=0; break; }}
            i=pp->pS; if (i>=0){ qq=pnt.dat+i; a=pp->a+pp->lS; if (qq->a>a) { qq->a=a; upd.add(i); } if (i==p0) { e=0; break; }}
            i=pp->pE; if (i>=0){ qq=pnt.dat+i; a=pp->a+pp->lE; if (qq->a>a) { qq->a=a; upd.add(i); } if (i==p0) { e=0; break; }}
            i=pp->pW; if (i>=0){ qq=pnt.dat+i; a=pp->a+pp->lW; if (qq->a>a) { qq->a=a; upd.add(i); } if (i==p0) { e=0; break; }}
            }
        // reconstruct path
        e=p0; pp=pnt.dat+e; path.add(e);
        for (;e!=p1;) // loop until path complete
            {
            a=0x7FFFFFFF; e=-1;
            // e = select link with smallest cost
            i=pp->pN; if (i>=0){ qq=pnt.dat+i; if (qq->a<a) { a=qq->a; e=i; }}
            i=pp->pS; if (i>=0){ qq=pnt.dat+i; if (qq->a<a) { a=qq->a; e=i; }}
            i=pp->pE; if (i>=0){ qq=pnt.dat+i; if (qq->a<a) { a=qq->a; e=i; }}
            i=pp->pW; if (i>=0){ qq=pnt.dat+i; if (qq->a<a) { a=qq->a; e=i; }}
            if (e<0) break; // dead end
            pp=pnt.dat+e; path.add(e);
            }
        }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    

    及用法:

    Graphics::TBitmap *maze=new Graphics::TBitmap;
    maze->LoadFromFile("maze.bmp");
    maze->HandleType=bmDIB;
    maze->PixelFormat=pf32bit;
    A_star_graph map;
    map.ld(maze,0);
    map.compute(0,map.pnt.num-1);
    map.draw(maze);
    

    代码是基于 VCL 的(使用第二个链接中描述的位图),我也使用我的动态列表模板:


    List&lt;double&gt; xxx;double xxx[]; 相同
    xxx.add(5);5 添加到列表末尾
    xxx[7]访问数组元素(安全)
    xxx.dat[7]访问数组元素(不安全但快速直接访问)
    xxx.num是数组实际使用的大小
    xxx.reset() 清除数组并设置xxx.num=0
    xxx.allocate(100)100 项目预分配空间

    抱歉,不是 python 编码器,但认为代码足够简单......所以我希望你可以毫无问题地移植/适应你的环境。

    这里输出:

    看起来它模仿了你的......代码仍然没有优化,可以进一步改进......我认为你应该仔细看看mx,mymxy[][],myx[][]变量。这些是拓扑索引排序的顶点,可以极大地加速您的代码...

    [编辑1]

    我更新了 A* 搜索代码(感谢 Matt Timmermans)所以这里是更新的结果:

    【讨论】:

    • 那不是A*,4.8ms对于这个问题来说并不快。您是否包括从磁盘加载的时间?
    • @MattTimmermans 没有全部内容(exe 初始化 + 加载 + 转换为图形 + 求解 + 渲染)达到 ~25ms。 OP 只有链接部分~500ms,这是我的观点……除了 A* 之外还有什么?然而,成本启发式和迭代形式可能具有欺骗性(基于顶点数而不是距离)所以我将其更改为距离现在添加了一些 cmets 和边缘情况的东西。但是 A* 本身并不重要,因为问题在于图的创建(至少这是我对 OP 问题的理解)
    • A* 使用优先级队列按照最不预期路径长度的顺序处理顶点。 A* 在迷宫中应该花费 O(N) 时间,但是您在每一步都在处理所有顶点,这使得您的算法花费 O(N^2) 时间。作为比较,这个版本在我的笔记本电脑上找到最短路径并在 0.16 毫秒内将其着色为绿色:pastebin.com/eDmf9srE,这大约是在不仔细优化的情况下需要多长时间。它使用 BFS,因为 A* 启发式在迷宫中的帮助不足以使其值得额外花费。
    • @MattTimmermans +1 图片。时间是4004ms,我的(未优化的天真)缓慢迭代......在实现que之后时间是~5.4ms我更新了代码并添加了更详细的时序测量结果图像。我认为它仍然可以进一步优化(忽略覆盖的顶点)
    • 所以现在你可以看到制作这些节点结构的问题——它比没有它们解决迷宫需要更长的时间。在没有任何额外数据结构的情况下解决迷宫只需要每个像素的少量工作,所以如果你想构建一些东西来加速它,它必须非常简单,因为你之前每个像素只能花费很少的工作太贵了。
    猜你喜欢
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2014-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 2014-09-25
    相关资源
    最近更新 更多