zoukankan      html  css  js  c++  java
  • [自带避雷针]DropShadowEffect导致内存暴涨

                              [自带避雷针]DropShadowEffect导致内存暴涨

                                                     周银辉 

    从学习WPF开始, 就知道"位图效果"不是什么省油的灯, 但我只知道它会占用很多cpu时间, 而显得效率低下, 但完全不料的是在某种特殊的硬件环境下其会导致内存暴涨.

    事情是这样的, 我写了一个小程序, 看起来还不错, 但并不变态的日本朋友在变态的硬件环境下测试我的程序说"操作一段时间后程序会崩溃". 我远程VNC过去,发现程序每进行一个小操作(甚至是点击一下鼠标)内存会增长好几M,  照这个速度, 一会准崩溃, NND

    但, 郁闷的事情是, 我无法重现这个Bug, 在开发团队和测试团队的机器上都没法重现(但在日本那边,这个bug是百分百重现)...内存泄露还没法重现, 好搞笑...更搞笑的事情在后面

    最后我发现日本那边采用了如下的显示器配置: 其主机上连接了3个显示器, 第一个为普通屏, 第二和第三个屏是医学专用的高清屏(传说分辨率超高,当然价格也超贵), 当连接上这两个屏幕的时候, 我的程序就死翘翘了. 首先排除单纯的多屏问题,因为我的开发机就是双屏的, 然后我又排除了高清屏的问题, 因为禁用掉普通屏,而使用高清屏的时候,一切OK.

    经过一系列的排查, 得到的诡异结论是: 当普通屏和高清屏同时启用, 并且我的程序窗口显示在普通屏上的时候, 内存暴涨, 最后死翘翘, 其他情况一切OK...

    为什么要在这么诡异的环境下才能重现这个问题呢, 完全无解.

    好吧, 我暂且承认是自己代码烂, 内存泄露了吧, 还好我有  .net memory profile ANTS Memory Profiler 

    经过一番折腾之后, 这两个被我奉为"神器"的内存工具并没有给我任何答案: 从变化曲线上看内存是增长了许多(动不动就上百兆), 但内存快照中各个类型对象的内存占有率以及变化率都非常地正常, 晕, 丢失的内存哪里去了~~

    我开始怀疑是P/Invoke之类的丢失了内存, 因为程序中有大量的win32 API平台调用, 然后,我注释掉了这些代码... 很不幸, 答案是NO.

    没辙, 再来一招: 功能裁剪,这是经常使用的一招, 这让我们比较容易地缩小代码范围. 由于UI层和后台代码的耦合度非常低, 直接注释掉UI层上的XAML代码, 功能就能被很好地裁掉, 而不用更改逻辑代码, 所以功能裁剪显得比较容易. 我的程序都快被裁成空壳了, 问题依然存在... 
    还有一个很搞怪的问题是, 比如我们定位到一个比较复杂的函数A会导致问题, 然后从A开始跟踪, 到B -> C -> D 绕了很长一圈后, 会到达一个及其简单的代码上比如 : this.width = myWidth; 注释到这个简单的代码就没问题, 否则导致问题, 诶, 难道是宽度的改变会引发什么事件,或者重写了控件重绘等等 而导致的问题? 这一点点希望很快就被抹杀了, 控件已经被我们抽取到足够的简单, 几乎没有什么代码.... 耍我么... 

    一般, 在我绝望的时候, 我会使用我的大绝招: 我猜 

    我一直比较相信写程序还是要靠灵感的(当然, 建立在你基础知识比较扎实的基础之上的). 那么我先猜是那个控件导致的问题呢, 我当然不会怀疑自己的代码咯, 我怀疑微软的代码, 这不是自大, 而是经验值, 我搞了不少无厘头的问题, 最后根源都在微软的代码上, 如果你有机会玩玩微软的RichTextBox控件再加上一个日文的Atok输入法, 你就会相信我说的话的(会搞死人的). 所以我猜ViewBox, 程序中的一个绘图面板放在ViewBox中(为实现缩放功能), 那好, 把绘图板从面板中剪切出来吧.

    哈哈, OK啦,  把绘图板从面板中剪切出来后真的没问题也, 难道我运气这么好, 一下就猜中了...

    为了验证, 我重新做了一个很简单的关于viewbox的DEMO, 放在日本那边的机器上, 没问题, 我晕

    然后我注意到源程序中, viewbox放在一个ScrollViewer中, 并且这个ScrollViewer有自己的模板,  もしかしてなの ...

    把画图板重新放回到ViewBox中, 在scrollViewer中动刀. 我发现, 我惊奇地发现:
     

    在该ScrollViewer控件模板中, 居然有一个DropShadowEffect, 天杀的. 另外, 这个effect在我们的程序里面看不到阴影效果, 我们的程序也不需要这个效果, 所以一直没察觉.

    删掉! 整个世界都安静了~

    为了证明这个的确会导致问题, 所以我做了一个简单的DEMO,  

    <Window x:Class="DropShadowEffectMomeryLeakDemo.Window1"
        xmlns
    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x
    ="http://schemas.microsoft.com/winfx/2006/xaml"
        Title
    ="Window1" Height="300" Width="300">
        
    <Canvas x:Name="canvas">
            
    <Canvas.Effect>
                
    <DropShadowEffect/>
            
    </Canvas.Effect>
            
    <Button Width="100" Height="30" Content="Click me" Click="Button_Click"/>
            
            
    <!--下面这些代码可以忽略, 我只是放了点普通控件,以便上内存泄露更明显-->
            
    <Rectangle Fill="White" Stroke="Black" Width="53" Height="43" Canvas.Left="37" Canvas.Top="71"/>
            
    <Rectangle Fill="#FFAF8C8C" Stroke="Black" Width="54" Height="72" Canvas.Left="158" Canvas.Top="42"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="54" Height="47" Canvas.Left="117" Canvas.Top="130"/>
            
    <Rectangle Fill="#FF21CB63" Stroke="Black" Width="44" Height="65" Canvas.Left="212" Canvas.Top="130"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="42" Height="49" Canvas.Left="58" Canvas.Top="177"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="53" Height="45" Canvas.Left="171" Canvas.Top="195"/>
            
    <Rectangle Fill="#FFDE13EE" Stroke="Black" Width="70" Height="69" Canvas.Left="224" Canvas.Top="30"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="21" Height="43" Canvas.Left="100" Canvas.Top="42"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="21" Height="22" Canvas.Left="171" Canvas.Top="8"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="37" Height="43" Canvas.Left="117" Canvas.Top="71"/>
            
    <Rectangle Fill="White" Stroke="Black" Width="41" Height="33" Canvas.Left="49" Canvas.Top="130"/>
            
    <Rectangle Fill="#FFFF2B2B" Stroke="Black" Width="54" Height="31" Canvas.Left="100" Canvas.Top="195"/>
        
    </Canvas>
    </Window>

     然后点击Button的时候,做如下操作:

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                
    for(int i=0; i<canvas.Children.Count; i++)
                {
                    var c 
    = (FrameworkElement) canvas.Children[i];
                    c.Width 
    += 1;
                    c.Height 
    += 1;
                }
            }

    在我的开发机上(在你的机器上可能也是), 连续点击button, 内存会有所增长, 但增长比较慢, 并且过一小会就释放带了, 所以内存基本维持到十多兆(图中18756K):
     

    放在日本那个特殊的环境下,情况就完全不一样了(下图99340K, 并且好玩的是, 下图有黄色桌面背景的是第一个显示器, 蓝色背景的是第二个显示器, 把window1拖到第二个显示器上内存表现会很正常哦) :
     

     恩, 人生在于折腾

    ps: 这台机器插了两张显卡: nVIDIA Quadro FX370 和 VREngine SMD5

  • 相关阅读:
    how to pass a Javabean to server In Model2 architecture.
    What is the Web Appliation Archive, abbreviation is "WAR"
    Understaning Javascript OO
    Genetic Fraud
    poj 3211 Washing Clothes
    poj 2385 Apple Catching
    Magic Star
    关于memset的用法几点
    c++ 函数
    zoj 2972 Hurdles of 110m
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1757277.html
Copyright © 2011-2022 走看看