RSS 2.0 Feed
2006-12 Entries
摘要:貌似PageParser的大量调用,会导致大量的小size的dll、众多memory hole的出现。得到eparg的指教,越来越感觉是这个样子。据joycode老大说,只有2.0这个样子,只有特定的这一个app这个样子。 准备写一个.net 2的测试程序,看这个hole是不是确实很明显? (详情等下文)...[阅读全文]

posted @ | Feedback (5) | Filed Under [ VS.NET ]

摘要:Session,Session,Session!(请耐心阅读………………) 地球人都知道,asp.net中有三种方式存放我们的session objects。In Proc模式,在cache中存放对象。StateServer在State Service中存放,最后一种是存放在SQL Server中。对于In Proc模式,太多的session对象,意味着高内存占用;对于后两者,意味着序列化和反序列化的性能损失。Session存放的东西太多,不一定意味着性能的问题,但这依赖于你往session里面存放的东西。让我们假设一个场景,你开发了一个网上商店,有很少的人在用,所以你只用了一台web server,使用了In Proc模式,你的程序对每个用户的订单,都存放了一些dataset。突然,你的程序出名了!一个大公司来找你,于是,你增加了N台web server,同时把Session State也修改成了SQL Server模式。 问题来了!asp.net进程,内存占用很好(800MB – 1GB),有时候会提示OutOfMemory异常,或者提示.net进程被recycled了。dump的大小为1,473,913 bytes,所以,大概就是1.4G。首先,我们看看托管堆上面都有些啥东西? 0:023> !eeheap -gcNumber of GC Heaps: 2------------------------------Heap 0 (0x000b7198)generation 0 starts at 0x022104d4generation 1 starts at 0x022037c0generation 2 starts at 0x02170030ephemeral segment allocation context: none segment    begin       allocated     size0x2170000 0x2170030  0x224a4e0 0xda4b0(894,128)Large object heap starts at 0x0a170030 segment    begin       allocated     size0x0a170000 0x0a170030  0x0acf0b20 0x00b80af0(12,061,424)0x0d490000 0x0d490030  0x0e3d2450 0x00f42420(16,000,032)0x12010000 0x12010030  0x12f52460 0x00f42430(16,000,048)0x13010000 0x13010030  0x13f52460 0x00f42430(16,000,048)0x15010000 0x15010030  0x15f52460 0x00f42430(16,000,048)0x1a010000 0x1a010030  0x1af52460 0x00f42430(16,000,048)…0x71ca0000 0x71ca0030  0x72be2470 0x00f42440(16,000,064)0x748b0000 0x748b0030  0x757f2470 0x00f42440(16,000,064)0x7d0e0000 0x7d0e0030  0x7d881250 0x007a1220(8,000,032)Heap Size  0x2d5b4e10(760,958,480)------------------------------Heap 1 (0x000ede88)generation 0 starts at 0x06249b58generation 1 starts at 0x0623e190generation 2 starts at 0x06170030ephemeral segment allocation context: none segment    begin      ......[阅读全文]

posted @ | Feedback (18) | Filed Under [ VS.NET ]

摘要:(序 & 跋)    此文及后面的系列,都是从tess老大那里翻译过来的。一直和GTEC的老牛们作CASE(此句有误,一直提CASE,等老牛们提供答案),算是粘到了一点仙气。偶一直比较懒,所以以前精心抄袭的文章,今天再次精心作序于此,希望对各位挣扎于现实与理想的各位,共享,共勉。tess老大的文章国内似乎有人翻译过,但偶个人观点,不看好,因为好东西都没了哦。    不是我不舍得,意思是,我的post里面基本上没有link,但是从google上都能搞到,如tess老大的系列debug文章。写程序的人,用好google应该是第一要素啊,哇哈哈哈!(百度除外,偶鄙视的公司)    每个post的题目都挺吓人,但是最终发现问题以及解决掉的方法,都异常简单。简单,产生丑陋。   问题描述: 程序慢的要死,CPU占用始终持续在70%-80%之间 解决步骤: 性能监视器。对于高CPU占用,一般的是这三个原因: ·         高的离谱的循环 ·         太多的加载(比如,许多小的对象被频繁的处理) ·         GC作了太多的事情 第一种情况,当你在恰当的时机抓到一个dump,就非常容易解决,一般而言,都是因为业务处理逻辑造成的。第二种情况,一般需要从硬件上考虑,scale up或者scale out,都行。 是否是GC的问题,我们需要看性能监视器里面的.NET CLR Memory计数器。这里面,最重要的是.net CLR Memory / % Time in GC. 这个值的阀值,可能是5%或者30%或者20%。实际上,没有一个准确的阀值存在的。当然,这个数字理论上应该接近于0%才对。 在GC里面,导致高CPU占用的原因,通常是因为过高的分配速率(对应到性能监视器里面的.net CLR Memory / allocated bytes/sec计数器。但实际上,如果所有的GC操作都在第0代上,则不会导致这个问题。真正的元凶,是大量的2代操作。如N多的对象在被移动到2代或者从2代中被释放。另一个原因就是我们熟知的大对象操作(LOH) 再强调一次,没有什么准确的指标,就告诉我们,超过了它就是出问题了,这是不可能的。包括微软给我们的大多数Practics Training,只有“尽量”、“尽可能”、“如果”等,而不是“一定”、“必须”。 如同我在那篇“浅谈GC中”讲到的一样,如果你搞了GC.Collect(2)或者GC.GetTotalMemory(true),那么也会导致大量的2代回收。 对于这个问题,我从性能监视器中抓到了这些数据: % Time in GC                        ~40 % averageallocated bytes / sec              400 MB average# Induced GC                        0# Gen 0 Collections                28.379# Gen 1 Collections                28.378# Gen 2 Collections                28.378 看第二行,真是让人晕倒!每秒分配400M字节!但实际上,我的代码中没有分配任何这么大的东西,有点太离谱了吧?!如果看最后面三行,也比较搞笑,0、1、2代的分配几乎完全相同。这实际上说明有LOH在压缩,或者有大量的对象冲进了第2代,然后又被立刻释放掉。 开始debug吧! GC问题很难debug,因为: 1. 如果在GC中间过程中用adplus -hang模式抓了一个dump,基本上从dump里面看不到任何高CPU占用的原因。2. 即时你通过性能监视器找到了GC的问题,也抓到了dump,但是很难分析。 实际上,通用的做法是每隔一小段时间,你就抓一次dump。如果看起来都差不多,那么有可能就能分析出哪里的问题了。 步骤1 - 我们在GC里面不? 如果我们的OS是server,那么每个CPU有一个GC线程(如果超线程的话,就是2个)。如果是非server的,那么只有一个GC线程。我们正在看到的这个dump,是在一个双核的、带有.NET2.0的机器上产生的。 如果我们不在GC过程中,那么我们会有两个线程(每个CPU一个):   18  Id: 134c.918 Suspend: 1 Teb: 7ffa8000 UnfrozenChildEBP RetAddr  Args to Child              01e1fe68 7c822124 77e6baa8 00000398 00000000 ntdll!KiFastSystemCallRet01e1fe6c 77e6baa8 00000398 00000000 00000000 ntdll!NtWaitForSingleObject+0xc01e1fedc 79e77fd1 00000398 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac01e1ff20 79e77f9a 00000398 ffffffff 00000000 mscorwks!PEImage::LoadImage+0x19901e1ff70 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x11701e1ff80 79f3549b ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x1701e1ffa8 79f6ece3......[阅读全文]

posted @ | Feedback (18) | Filed Under [ VS.NET ]

摘要:上文中,我们解决了那个场景的死锁问题。这次,我们分析一下,为什么会死锁呢?再回顾一下两个sp的写法:   CREATE PROC p1 @p1 int AS       SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1   GO   CREATE PROC p2 @p1 int AS         UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1         UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1   GO    很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!   那么,什么导致了死锁?    需要从事件日志中,看sql的死锁信息:   Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”):    SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1   Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”):    UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1                   The SELECT is waiting for a Shared KEY lock......[阅读全文]

posted @ | Feedback (18) | Filed Under [ Solution ]

摘要:   死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:   trans1                                            trans2   ------------------------------------------------------------------------   1.IDBConnection.BeginTransaction   1.IDBConnection.BeginTransaction   2.update table A                            2.update table B   3.update table B                            3.update table A   4.IDBConnection.Commit                4.IDBConnection.Commit    那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。    好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。   -- Batch #1   CREATE DATABASE deadlocktest   GO   USE deadlocktest   SET NOCOUNT ON   DBCC TRACEON (1222, -1)   -- 在SQL2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc       --traceon(1204,3605,-1)看到所有的死锁信息。SqlServer 2005中,对于1204进行了增强,这就是1222。   GO         IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1   IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1   IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2   GO   CREATE TABLE t1 (c1 int, c2 int, c3 int, c4 char(5000))   GO   DECLARE @x int   SET @x = 1   WHILE (@x <= 1000) BEGIN            INSERT INTO t1 VALUES (@x*2, @x*2, @x*2, @x*2)            SET @x = @x + 1   END   GO   CREATE CLUSTERED INDEX cidx ON t1 (c1)   CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)   GO   CREATE PROC p1 @p1 int AS SELECT c2, c3 FROM t1 WHERE......[阅读全文]

posted @ | Feedback (23) | Filed Under [ Solution ]

摘要:string bytecode = "73 01 00 00 06 28 4E 00 00 0A 2A";上面这个string干什么的?加密后的代码?某种不可思议的数字?汇编语言? 如果按照CLR IL规范来解析,我们会得到一段常见的代码。0000 : newobj instance void WindowsApplication3.MainForm::.ctor()0005 : call System.Void System.Windows.Forms.Application::Run()0010 : ret 简单吗?看起来是new一个MainForm,然后call Application的Run方法,最后return。仔细想想,这就是我们常见的,每个winform程序中缺省方式下自动生成的代码。如下:static void Main() {   Application.Run(new MainForm());}实际上等同于:static void Main() {   MainForm mf = new MainForm();   Application.Run(mf);} 那么,上面那个bytecode,是如何被翻译成上述代码的? 哦,bytecode的第一个字符,就是73。有一个神奇的表格,其中一行:73 newobj <Method> N arguments o 我们看到了,73代表一个东西叫做newobj,并且,它是一个method。我们应该能猜测出来,C#代码中应该就是一个new关键字。 第二个数字01,我们还是查找那个表格:01 break - - - 哦,怎么是break呢?我们代码里面,不大可能new了一个对象之后,立刻就break啊?实际上,第一句newobj之后,执行的代码是从0x05,就是0x28开始(第二句) 。为什么跳跃了5个字节呢?因为IL中,并不只是包含operator,还包含了其他数据。在CLR解析过程中,它发现0x73是一个method之后,会继续取得一个所谓的metadataToken。伪代码如下:metadataToken = ReadInt32(il, ref position);而ReadInt32代码,如下所示:private int ReadInt32(byte[] _il, ref int position){   return (((il[position++] | (il[position++] << 8)) | (il[position++] << 0x10)) | (il[position++] << 0x18));} 为什么会这么做???暂且不表,我们继续。通过上面的ReadInt32,position增加了4,变成了5。所以要从bytecode第0个字节跳跃5个,直奔0x28。通过查表,我们知道,0x28代表着一个call:28 call <Method> N arguments Ret.value。通过某种手段,我们可以得到当前0x28的call的方法名,是run。 那么首先,那个神奇的表格是什么呢?就是一个二维信息,从0x00到0xFE,每个字节分别对应着某个操作,如br,call,newobj等。这个表格,可以从《Inside Microsoft.NET IL Assembler eBook》中找到。我们查表查找到operator之后,通过一系列的Read*方法,得到metadatatoken。针对这个token,分别进行resolve,得到最后的operand。如上所述,通过Read*系列方法,我们同时移动了bytecode中的offset,可以定位到正确的下一个operator。 这段解析代码,可以到codeproject上查找SDILReader,里面的Globals.cs、ILInstruction.cs、MethodBodyReader.cs,分别对应着全局operators、IL构造、方法转换等内容。...[阅读全文]

posted @ | Feedback (12) | Filed Under [ VS.NET ]

摘要: 名次解释 DMVs:dynamic management views 三个点 · 资源瓶颈: CPU、内存、I/O(这里面不考虑网络问题) · Tempdb瓶颈: · User query瓶颈,可能是统计信息的变化、不恰当的索引、阻塞或者死锁等 上述三点,可能是相互影响的。 资源瓶颈   工具 1. System Monitor (PerfMon):windows自带 2. SQL Server Profiler: 2005继续有 3. DBCC commands: 参考联机文档 4. DMVs: 见上名次解释   CPU瓶颈 CPU瓶颈,是突然并且不可预料的。一般来讲,没有优化的查询计划、系统低配置、设计不合理等,很容易导致这些问题。 在perfmon中,我们一般需要监视Processor:% Processor Time,如果每个CPU持续高于80%,CPU就是瓶颈了。当然,在强大的2005下我们也可监视sys.dm_os_schedulers ,如果有内容,表明有任务等待CPU来分配给它。如下面这个DMVs的查询: select scheduler_id,current_tasks_count,runnable_tasks_count from sys.dm_os_schedulers where scheduler_id < 255 下面的查询,更高级点。分析方法是,看结果的number_of_statements,如果该值大于1,说明可能有问题,要进一步分析。 select top 50 sum(qs.total_worker_time) as total_cpu_time, sum(qs.execution_count) as total_execution_count,count(*) as number_of_statements, qs.plan_handle from sys.dm_exec_query_stats qs group by qs.plan_handle order by sum(qs.total_worker_time) desc 执行计划的编译与重新编译 在sql2005中的一个改进,就是对于某个sp,进行recompile的时候,只需要针对改变的部分进行编译,sql2000只能是全部都搞一遍。 Recompile的原因很多,如: · Schema的变更 changed · Statistics变更 · 延迟编译 · SET option的执行 · 临时表的变化 · Sp使用了RECOMPILE提示或者使用了OPTION (RECOMPILE)   诊断方法,老朋友了,继续使用perfmon或者sql profiler。 对于perfmon,监视下面的 计数器 · SQL Server: SQL Statistics: Batch Requests/sec · SQL Server: SQL Statistics: SQL Compilations/sec · SQL Server: SQL Statistics: SQL Recompilations/sec 对于profiler抓到的trace,分析这几个event:SP:Recompile / SQL:StmtRecompile / Showplan XML For Query Compile。如果我们抓到了trace,对于文件,可以这么做: select spid,StartTime,Textdata,EventSubclass,ObjectID,SQLHandle from fn_trace_gettable ( 'e:\recompiletrace.trc' , 1) where EventClass in(37,75,166) 这里面,EventClass 37 = Sp:Recompile, 75 =......[阅读全文]

posted @ | Feedback (9) | Filed Under [ Solution ]

摘要:龟速查询 阻塞和索引问题,是常见的导致sql以龟速执行的罪魁。 阻塞阻塞主要等待逻辑锁,如请求一个X锁。关于锁的信息,遍地都是,msdn或者google都可以。SQL Server 2005提供了125中等待类型(2000是76种)。 假设我们sp_who看到了一个block在56号上,那么通过这个可以看到详细信息 select * from sys.dm_os_waiting_tasks where session_id=56 (在2000下,你可以通过dbcc inputbuffer(56)来看当前执行的文本) 0x022A8898 56  0  1103500   LCK_M_S  0x03696820  0x022A8D48  53  NULL  ridlock fileid=1  pageid=143 dbid=9 id=lock3667d00 mode=X  associatedObjectId=72057594038321152 这里显示,56被53阻塞,并且等待了1103500毫秒了。 通过使用sys.dm_tran_locks,我们可以看到56被53以X模式锁住了,53持有1:143:3这个资源。 select request_session_id as spid, resource_type as rt,  resource_database_id as rdb,  (case resource_type WHEN 'OBJECT' then object_name(resource_associated_entity_id) WHEN 'DATABASE' then ' ' ELSE (select object_name(object_id)  from sys.partitions  where  obt_id=resource_associated_entity_id)    END) as objname,  resource_description as rd,     request_mode as rm, request_status as rs from sys.dm_tran_locks 输出如下 spid     rt           rdb         objname       rd            rm           rs -----------------------------------------------------------------------------  56    DATABASE     9                               S          GRANT 53    DATABASE     9                              S          GRANT 56    PAGE          9       t_lock      1:143       IS         GRANT 53    PAGE        9       t_lock      1:143       IX         GRANT 53    PAGE          9       t_lock      1:153       IX         GRANT 56   OBJECT       9       t_lock                  IS         GRANT 53   OBJECT       9        t_lock                 IX         GRANT 53    KEY         9        t_lock      (a400c34cb X          GRANT 53    RID         9        t_lock      1:143:3    X         ......[阅读全文]

posted @