易码技术论坛

 找回密码
 加入易码
搜索
楼主: FantasyDR

第一帖就发个软件吧——GVmakerAD

[复制链接]
发表于 2009-10-7 23:43:43 | 显示全部楼层
初略估计,这个update的实现会使程序运行效率降低一半以上。
因为里面
1.每次生成一个不必要的IntBuffer,这个buffer的内部实现应该包含了一个int数组,这样每次生成一个160*80的int数组,不仅效率低下,还会对GC带来很大的负担。
2.从mBuffer拷贝数据到buffer,然后再从buffer拷贝数据到mBitmap,这里至少做了一半的无用功。
事实上只需直接拷贝一部分mBuffer的数据到mBitmap即可

当然,我只看了这一部分代码;另一方面我没写过Android的程序,也许考虑的不对;
但如果没弄错的话,只需要将update的代码去掉,将与mDirty相关的代码去掉,然后修改ScreenPane构造函数的代码为:
  1.     public ScreenPane(JGVM gvm) {
  2.         mBitmap = Bitmap.createBitmap(ScreenModel.WIDTH, ScreenModel.HEIGHT, Bitmap.Config.ARGB_8888);
  3.         gvm.setColor(0xff000000, 0xffffffff);
  4.         
  5.         mBufferRect = new Rect(0, 0, ScreenModel.WIDTH, ScreenModel.HEIGHT);
  6.         setSize(ScreenModel.WIDTH, ScreenModel.HEIGHT);
  7.         
  8.         gvm.addScreenChangeListener(new ScreenChangeListener() {
  9.                
  10.             public void screenChanged(ScreenModel screenModel, Area area) {
  11.                 screenModel.getRGB(mBuffer, area, 1, 0);
  12.                 mBitmap.copyPixels(mBuffer,0,ScreenModel.WIDTH,area.getX(),area.getY(),area.getWidth(),area.getHeight);
  13.                canvas.drawBitmap(mBitmap, ....); //这里没去看文档了,大概是这样使用吧
  14.             }
  15.             
  16.         });
  17.     }
复制代码
这样性能应该可以提高一倍以上。
发表于 2009-10-7 23:45:03 | 显示全部楼层
BTW: f-u-c-k G-F-W,看个Android在线文档还要开代理。。。
 楼主| 发表于 2009-10-8 11:28:56 | 显示全部楼层
原帖由 Eastsun 于 2009-10-7 20:42 发表


用8bit应该是最快的方式
但也不至于会造成慢吧,我多普达586W才200MHZ的CPU还挺流畅的
至于按键阻塞,这不是应该的么?
至于解释器以固定的频率工作,这个需求不是必要的吧?快一点一般没多大影响,大部分GVmaker程序都有自己 ...


我的意思是,阻塞的应该是指令的执行,而不是解释器本身。tick解释器的地方如果阻塞了,那要退出的时候就只好杀线程了。否则应该是发给解释器一个类似关机中断的玩意儿过去,解释器自己就知道要退了。想象中模拟器模拟的是CPU的流水线,不应该卡住的说。

关于渲染上的延迟,我也没有对比过,看fps的话好像只有十几,人眼还是可以察觉的。
发表于 2009-10-8 18:15:40 | 显示全部楼层
现在的关键问题是性能问题,我觉得主要出在你那个update方法中
那个IntBuffer完全是多余的,即使你要用IntBuffer,你也可以使用IntBuffer的静态方法wrap方法将mBuffer包装成一个IntBuffer就OK了
 楼主| 发表于 2009-10-9 13:10:11 | 显示全部楼层
那个update的确效率巨低……以前加了个TODO在上面,是准备直接用mBuffer的,不过现在我也不记得当初要干点啥了……汗,无论如何,直接用mBuffer之后能快不少~~

那个mDirty是为了通知其他线程对屏幕进行更新,如果一个tick里面更新过不止一次屏幕的话,这的确会导致跳帧。可能当初这样做是由于线程同步上遇到了一些问题,具体原因我也忘了=_=

你在21楼提到的方式,主要是在那个callback里面拿不到窗口的canvas,这样无法更新真正的屏幕上。我再琢磨琢磨怎么绕一下把canvas传进去,本来是不想把android的东西搅和进来的。。。

我发现跳帧在某些游戏上很明显,但是我没有看nextStep()是什么时候返回的,这么说的确有可能在一个tick里面更新多次屏幕?
发表于 2009-10-9 15:02:51 | 显示全部楼层
原帖由 FantasyDR 于 2009-10-9 13:10 发表
你在21楼提到的方式,主要是在那个callback里面拿不到窗口的canvas,这样无法更新真正的屏幕上。我再琢磨琢磨怎么绕一下把canvas传进去,本来是不想把android的东西搅和进来的。。。


我不清楚Android的GUI框架,不过我想思路应该都差不多(本想去看看Android的在线docs,被G-F-W毙掉了):
在ScreenChangeListener中先将GVmaker的图像数据复制到一个数组,然后将这个数值的数据赋给一个image,然后通知Android要刷新屏幕;这样只要在窗口的绘制方法中添加绘制image的代码就OK了

就是说不用考虑怎样将canvas绕进去,只要让窗口知道这个image的存在就可以了。

{无论是Swing还是J2ME中,窗口绘制都差不多:把绘制代码加到窗口的paint方法中,当需要重绘窗口时,调用窗口的repaint方法告诉它:你该调用paint方法了}
发表于 2009-10-9 15:23:46 | 显示全部楼层
看了下,估计差不多是这样子:

1、在MainView中添加一个paint方法://可能需要添加try finally
  1. public void paint(Bitmap image){
  2.      SurfaceHolder holder = getSurfaceHolder();
  3.      Canvas canvas = holder.lockCanvas(null);
  4.      canvas.drawBitmap(image, ...);
  5.      holder.unlockCanvasAndPost(c)
  6. }
复制代码
2.把我上面21楼代码中最后一行改为://view就是MainView的引用
  1. view.paint(mBitmap);
复制代码
发表于 2009-10-9 15:30:37 | 显示全部楼层
貌似这样使用在canvas绘制image的时候会阻塞线程,虽然可以保证实时绘制到屏幕
更好的方法应该是将绘制代码添加到View的onDraw方法中,然后在screenChanged方法的最后添加view.postInvalidate();通知View绘制,这样可以保证JGVM流畅运行
 楼主| 发表于 2009-10-9 22:55:35 | 显示全部楼层
嗯嗯,我前面的意思应该是说把view绕进去,而不是把canvas绕进去……我对android的这陀东西也不熟悉的说=_=

目前的实现中,ScreenPlan对上层的对象一无所知,纯粹让上层的对象自己来控制,ScreenPlan相当于一个数据源。这造成了那个lazy update的实现,因为么有直接关联onDraw和screenChanged,其实这两个东西应该是直接关联的,否则屏幕上的变化就不能即时显现了。

你上面说的这个方式控制屏幕更新应该可行,我抽空改改代码试试看。

关于线程的事情,在目前的实现里,主要问题是JGVM的实例被多个线程共享。一是工作线程,负责tick,让解释器运转。另外还有UI的线程在控制,比如load按钮被调用,或者exit按钮被调用。其实这样有点乱,到时候干脆把jgvm的实例隐藏起来,外部对他的控制全部放到一个工作队列里面在同一个线程中执行就好了。
发表于 2009-10-10 09:31:38 | 显示全部楼层
但其实就工作线程是由程序产程的,UI线程是JVM本身所具有的~
当然你可以对JGVM再进行一层封装,把工作线程对外隐藏起来。
不过我当初一方面是出于方便调试,另一方面出于灵活性考虑,而且其实工作线程要做的事情挺简单的,就是一个循环调用nextStep而已
我觉得你的实现弄的太复杂了,MainView弄了一坨代码绕来绕去,然后还有一个控制帧速的代码,这些都是不需要的。

简单说来要做的事就是:
1.在JGVM的ScreenChangeListener中加入刷新屏幕的代码(无需专门去控制帧速,在这里把图像数据copy出来并通知机器刷屏就OK了)
2.每次载入GVmaker后new一个新的工作线程去执行GVmaker,工作线程里所需的逻辑就是循环执行step,并有一个知道何时推出循环。
 楼主| 发表于 2009-10-10 14:05:10 | 显示全部楼层
MainView里面没有控制帧速的代码,那个只是统计用的,用来统计解释器的运行频率。

刚刚重新理了理代码,发现我用的是SurfaceView而不是普通的View,所以onDraw这个方法是无效的,postInvalidate()也是无效的,必须拿到当前的SurfaceHolder然后得到canvas进行渲染。但是Surface可能会被重新创建或者销毁,因此不能让JGVM的ScreenPane保持ScreenHolder的引用。干脆还是在screenChang的callback中直接更新屏幕,避免掉帧,锁就锁吧~

这份代码最初是从J2SE版本改出来的,本想在对象的设计上和原来一一对应,引入了MainView之后,没有删掉ScreenPane,导致很多数据得在两个东西之间绕来绕去,其实MainView和ScreenPane合二为一就可以了。

我check-in了更新的代码,简单起见就直接让MainView来响应screenChange了,掉帧的问题应该是修掉了:)

关于JGVM,我主要是觉得直接interrupt thread的方式太突兀了,虽然实现起来很直接,但是容易失控。之所以这么觉得大概是因为我不了解Java的线程被interrupt的时候会发生什么吧~

我觉得主要原因在于JGVM的nextStep(),它的执行非常不确定,既可能执行很多指令,还可能阻塞。这导致了必须通过interrupt的方式才能让VM停下来。

不过目前看起来一切正常,等出问题再说吧~~
发表于 2009-10-10 16:15:47 | 显示全部楼层
我觉得主要原因在于JGVM的nextStep(),它的执行非常不确定,既可能执行很多指令,还可能阻塞。这导致了必须通过interrupt的方式才能让VM停下来。


因为我想不出更好的办法,在我想来,类似于sleep,getchar(均指GVmaker指令)必然会导致阻塞。
“既可能执行很多指令”?
每一个nextStep只会执行一个GVmaker指令Y

至于interrupt,据我了解并不复杂:
对线程A执行interrupt()方法后,会发生的事情:
1.标记该线程状态为interrupted
2.如果该线程处于阻塞状态(e.g.wait,sleep,I/O block..),终止阻塞状态,并抛出InterruptedException异常

在JGVM里面就是为了停止正在执行的sleep,getchar之类的指令,理解返回
发表于 2009-10-10 16:19:23 | 显示全部楼层
你是担心interrupt()会造成资源泄漏?
这个应该不会有问题,JGVM的dispose方法会负责资源的清除工具;即便中间还有其他问题,还会有JVM做最后的清理。
 楼主| 发表于 2009-10-10 18:02:13 | 显示全部楼层
1、如果只执行一条指令的话,不应该造成跳帧啊。因为原来的屏幕更新方法是在nextStep()返回之后立即检查是否dirty,如果dirty就更新屏幕。除非nextStep()之中进行了两次以上的屏幕更新操作,否则每帧画面都会被绘制。前面我看到你说mDirty会引起跳帧我就以为nextStep()中可能会多次更新屏幕……

2、我担心的不是资源泄露,就是觉得有点不习惯:)跨线程操作除非万不得已否则我不怎么喜欢用这种强制的方式。类似sleep或者getchar的方法,解释器可以在内部空转,并不一定非得阻塞nextStep()。就好比渲染的过程中如果要暂停,也不是非得停止更新屏幕,只要停止更新动画即可——内容保持静态但屏幕依旧以一定频率在更新。这样的话,对外表现是一致的,控制线程始终能够获得控制权而不会被JGVM卡死。
发表于 2009-10-10 21:05:36 | 显示全部楼层
1.我没细看你的代码,之前以为你新开了一个线程负责刷屏,然后保持屏幕以一定的帧速刷新。。。
  不过还是不明白你弄个dirty干嘛?直接在screenChangeListener里面得到刷屏信息后就直接刷新机器屏幕不就OK了,为什么还要通过dirty绕一下?
2.interrupt()方法既然存在,就是用来用的。。。
  我不明白你说的空转是什么意思
  如果有这样两条指令: 1.getChar  ->  2.eq 'c'
  那么nextStep执行getChar还没得到实际char值就返回了,那再调用nextStep怎么办呢?继续空转?
  换句话说,如果nextStep不造成阻塞,那么我怎么知道nextStep确实已经完成执行了所要执行的指令?

或者你的意思是JGVM内部维持一个线程,使其对外部JGVM的使用者不可见?不管他执行到哪里,让它执行就执行,停就停?

PS:对于可能阻塞的线程,貌似只有interrupt()方法让该线程安全退出
 楼主| 发表于 2009-10-10 23:26:06 | 显示全部楼层
因为注册在screenChangeListener上的对象ScreenPlan没有屏幕控制权,只是把屏幕画面储存在了bitmap对象里面。外层的MainView才有能力对屏幕进行操作,所以MainView需要检测是否得到了新的画面要更新屏幕~~这个设计的确是冗余的,历史问题=_=

线程中断的方式怎样都可以,习惯问题吧。就好像win32的线程,一样可以调用系统API杀掉,但更安全的方式是让线程执行结束自然退出。Java虽然有gc,但在我看来总体思想是类似的……

VM内部应该有类似指令寄存器吧。遇到getchar,就检查缓冲区,看是否有输入的字符。没有的话,IP保持原位。如果有输入字符,那么IP++,指向下一个指令。

事实上,硬要说的话getchar这种东东本身不应该理解为一条指令,而是一个复杂的执行过程,在VM上应该是个函数调用吧。而函数的实现又应该对应一系列指令,这个实现里面也应该存在一个类似我上面所描述的循环,用于检查缓冲区是否有字符输入。

sleep也类似,不一定要阻塞在哪里等够时间,可以等待1ms,然后退出,再次进入之后继续等待,直到等够了再让IP++。或者就直接把sleep作为函数调用,跳转到某坨个包含时间检查的循环,让VM自己去解释这堆指令来实现sleep的效果。

当然,这样会写得比较麻烦,毕竟java已经有现成的sleep。所以,为了简化,没啥特殊需求也没必要这么做……
发表于 2009-10-11 01:08:27 | 显示全部楼层
我想我明白你的意思了~
我们对于nextStep这个方法的意思理解可能不一样
我的理解就是GVM执行GVmaker的一个伪字节码——不管这个字节码是一个简单的赋值操作,还是一个复杂的getchar——而且是完全执行,必须得到结果方可。
而你对nextStep的理解,貌似类似于机器的一个工作单位


你是按照模拟CPU的行为去实现VM
但对VM来说,一个伪字节码的颗粒度与CPU上的一个指令是不能一一对应的
就说getchar,对VM来说就应该算一条指令,因为它对应的就是一个字节码;当然VM具体实现这个指令是要做检查缓冲区之类的事情,但这对GVmaker来说是不可见的,因为GVmaker程序没有检查缓冲区之类的功能,它必须依赖于VM;而且对于文曲星上的GVM,是可以通过检查键盘缓冲来实现,而这个实现是通过汇编来实现的,所以你说的“函数的实现又应该对应一系列指令”其实是对GVmaker的每一个伪字节码都对应一系列底层VM的指令,但这两者意义不一样;


至于线程的中断方式,以我的理解,在Java里面唯一安全的方式就是使用interrupt()方法来退出(当然设置标志位正常退出循环不在考虑范围)。虽然Thread还有stop和destroy方法,但这两个方法已经被Deprecated了。
因为他们是不安全的,可能造成对象数据处于一种不正确的状态(比如线程里面正好在new 一个String,new执行到一半线程终止了,可能这个String对象里某些属性被正确赋值了,而其他属性来不及被正确赋值)。
发表于 2009-10-11 01:18:25 | 显示全部楼层
BTW: 修改之后性能如何?

BTW2:
刚看了一下代码,有WorkerThread的run方法里面有这样:
  1. while (mRun && !isInterrupted()) {
  2.                                        
  3.                                         if (isPaused()) {
  4.                                                 continue;
  5.                                         }
  6.                                        
  7.                                         if (!mVM.isEnd()) {
  8.                                                 
复制代码
建议在 if (isPaused()) {
                                                continue;
                                        }
的continue前加一个Thread.sleep(xx);
以我的经验,在PC上含空循环的Thread非常耗CPU;不过或许Android的线程调度机制和PC上不一样,结果也会很不一样。
 楼主| 发表于 2009-10-11 13:50:06 | 显示全部楼层
原帖由 Eastsun 于 2009-10-11 01:08 发表
我想我明白你的意思了~
我们对于nextStep这个方法的意思理解可能不一样
我的理解就是GVM执行GVmaker的一个伪字节码——不管这个字节码是一个简单的赋值操作,还是一个复杂的getchar——而且是完全执行,必须得到结果方可 ...


我所谓的安全退出是说让线程自己结束,外部给它一个信号,然后线程得到通知后退出。interrupt方式可能java的解释器做了一些处理,相对安全一些,但毕竟还是强制中断的,线程可能在任何一个点被退掉,我们无法得知具体的出口在哪里。我的意思就是让线程的出口已知,不是说用stop或者destroy,连interrupt都不安全更不用说这两个方法咯:)

这意味着没有固定的模式,必须具体问题具体设计,来控制线程的退出。因为在线程里面跑着的代码路径可能千奇百怪。

但是,VM的工作方式和上面这个话题有关联。如果设计成你那种方式,工作线程只能被interrupt才能结束解释器的运行。而且,这个解释器的模块被限制必须放在独立的线程中去操作,用户无法在单线程的情况下使用它,因为它有可能把线程阻塞。

当然,无论如何原来的设计在这里都是合理的,也是完全可以正常工作的。

PS:直接用那个buffer之后效率提升了不少,后来的改动倒是没什么可察觉的速度变化,但毕竟代码清晰了不少:)
发表于 2009-10-11 18:37:17 | 显示全部楼层
能不能把程序发到附近里,菜场被强了,下不了。
您需要登录后才可以回帖 登录 | 加入易码

本版积分规则

Archiver|手机版|小黑屋|EMAX Studio

GMT+8, 2024-3-28 21:21 , Processed in 0.016662 second(s), 16 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表