無聊的宅男沒事又改起這個Live Writer的外掛程式了。原本的版本還不錯用,不過就是覺的少了點什麼… 除了加個框之外,跟網站版本也沒什麼差別嘛。用了一陣子,又加了兩個小功能上去…
COPY CODE
這個功能是從MSDN學來的,MSDN文章的範例程式碼,都會附個鈕讓讀者按,按一下程式碼就會被覆製到簡貼簿… 這個功能還蠻實用的,因為我常常這樣貼… 哈哈,不曉得看我 BLOG 的人有沒有這習慣? 不管了,我的BLOG,我寫的PLUGINS,我說好用的東西當然要加上去… 底下是改版後的外掛程式,張貼程式碼的樣子:
—————-[以下開始]——————
測試用的 C# Code Sample..
//
// 不重要的程式碼... 拿來當 Model 用的...
//
private int CountLeadingSpaces(string line)
{
int count = 0;
foreach (char ch in line)
{
if (ch == ' ')
{
count++;
}
else
{
break;
}
}
return count;
}
—————-[結束]———————–
HTML PREVIEW
另一個無聊的功能,是過去在寫些HTML相關的文章,常常要做這樣的動作: 一方面要想辦法把HTML秀到網頁上,就得用這種外掛來處理,不過另一方面又要讓讀者直接看一下HTML顯示出來的效果,一樣的CODE又要切到HTML編輯模式貼一次… 這次就是要省掉這個懶人工夫… 一次到位。來試一下這個功能:
—————-[以下開始]——————
HTML測試
<H3>這是H3的效果</H3>
<H3>這是H3的效果</H3>
<H3>這是H3的效果</H3>
<H3>這是H3的效果</H3>
<H3>這是H3的效果</H3>
HTML Preview
—————-[結束]———————–
好,展示完畢,沒什麼突破的進展,純粹自己好用而以[H]。我也不曉得有沒有人在用,懶的打包放網站了,需要的人再跟我要…
最近常常貼一些需要附上程式碼的文章, 我都借助 c# code format 這網站幫忙轉, 轉成好看一點的 HTML code.. 然後 Live Writer 切到原始碼的模式去改 HTML, 然後再切回來際續編…
人果然是懶惰的動物, 之前久久寫一篇還好, 最近就開始不耐煩了… 試了一套 Syntax Highlight 的 WLW plugins, 畫面不錯, 不過中文會亂掉.. 想說 c# code format 這網站的主人有 share source code, 我就把它拿來包成 Windows Live Writer Plugins 好了…
就是這念頭開始寫這個 project, 蠻好寫的, 兩三個小時過去就堪用了, 經過幾天試用慢慢改成現在的樣子, 先現寶一下, 放幾張圖:
[圖 1] 編輯畫面
[圖 2] 預覽畫面 (底下當然要加點廣告… )
結果就不用貼圖了, 底下這段就是用這 plugins 貼進來的…
[程式 1] 這是測試程式
using System;
using System.IO;
using System.Threading;
public class Program {
public static void Main(string[] args) {
Console.WriteLine("Hello!!" );
}
}
看起來效果還不錯, 雖然跟之前差不多, 不過手工的部份少很多, 貼上, 按 OK, 就收工了! 這個 c# code format 提供的 library 還不賴, 效果也是我試用幾種 lib 後比較滿意的, 滿意的地方是:
當初最主要用它的原因就是 (3), 其它捨棄 CSS 的結果, 就是產生出來的 HTML 參著一大堆 color code, 老實說這種 HTML code 看起來就很痛苦. 我是不想看啦, 不過我必需切到 HTML view 去貼上這堆字啊… c# code format 雖然要另外補上 .css, 不過看起來就清爽多了. 我直接把它附的 CSS 貼到我用的 community server 的 custom themes 裡 (部落格管理裡面就可以直接加, 不用改檔案), 用起來就很輕鬆愉快了 :D
要來看 code 嗎? 其實 code 就沒什麼好看的了, 需要的直接抓回去看吧. 倒是不常寫 WinForm 的我, 竟然被內建的 ComboBox 小整了一下… WinForm 內建的 ComboBox 功能很完整, Items 可以放 object, 然後再指定 ValueMember, DisplayMember… blah blah. 當然也有直接提供最簡單的 Text Editor, 一行字就是一個 Item …
不過, 我要的是很簡單的 Value / Display 分別指定就好, 就是這個 plugins 讓 user 選擇格式的地方 (如上圖), 我希望第一項的 Value 是 “HTML”, 而顯示的是 “HTML / XML / ASP.NET”, 這樣簡單的要求, 我心裡想… 這麼簡單, 一定可以直接用 Designer 填一填就搞定, 不用再去寫 code, 就可以 init 完成..
沒想到找了半天還真的找不到! :@ 翻了 MSDN, Microsoft community 等等技術支援網站通通都沒有. 教的都是一堆我覺的拖褲子放屁的作法… 不過是五個固定的選單而以啊…
到最後, 宣告放棄, 妥協了… 我這個功能最後是用這幾行 code 搞定的… ㄨ!!! 本來一行 code 都不想寫的…
替 ComboBox 設定初始值的程式碼片段:
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
comboBox1.Items.Add(new KeyValuePair<string, string>("HTML", "HTML / XML / ASP.NET"));
comboBox1.Items.Add(new KeyValuePair<string, string>("CS", "C#"));
comboBox1.Items.Add(new KeyValuePair<string, string>("VB", "Visual Basic.NET"));
comboBox1.Items.Add(new KeyValuePair<string, string>("MSH", "MSH (PowerShell)"));
comboBox1.Items.Add(new KeyValuePair<string, string>("SQL", "T-SQL"));
comboBox1.SelectedIndex = 1;
哈, 最後這邊收的不大漂亮, 不過不管了, 還好沒幾行. 這個 plugins 需要的就自己抓去用吧, 以後可能會不定時更新. 有啥改進意見可以留話給我, 不過嘛, 當然是有空 & 想改才有動力去開 visual studio .. [H]
– 下載: code formatter plugins
查了文件, 才發現可以這樣用… 平常連到 server 用的遠端桌面連現, 常碰到幾個問題:
原來這些都有解啊… (1) 最簡單, 把設定存檔就好, 就附圖的資訊, 底下有 [Save As], 以後直接點兩下存好的檔案就好了.
再來, (2) 跟 (3) 其實也有解, 只要先打開 DOS Prompt, 輸入 MSTSC /? 就會出現這個說明畫面:
答案就在影片中… 加上 /w:1440 /h:900 參數, 就可以用寬螢幕的解析度 1440 x 900 來搖控遠端的 server 了. 想要看 console (本機) 的畫面嘛? 比如有時 service 的 error message 只會秀在 console.. 這時只要加上 /console 參數就好. 整段指令如下:
開出來的視窗:
嗯, 看寬螢幕的果然比較爽, 當然這樣也就有機會用雙螢幕了. 小技巧, 需要的人可以參考看看!
續 上篇 & 上上篇 ,同樣的問題,我改用 .NET 開發是不是就搞定了? 其實這篇才是我要寫的重點,只不過引言寫太高興,就是兩篇文章了,咳咳… 有人在問,為什麼我老是寫些冷門的文章? 沒辦法… 大家都在寫的東西我就沒興趣寫了,文筆沒別人好,網站沒別人漂亮,連範例程式都沒別人炫,只好挑些沒人寫的內容…
大部份討論這主題的文章,講的都是 GC, GC 的 generation,IDisposable,還有 Heap 等等,不過這些知識都無法直接回答這次問題。底下的例子你會發現,預設的 GC 也無法解決 memory fragment 的問題,不過實際上是有解的,只是還要動用到秘技…
回題,先來看看之前的問題為什麼會是個問題? 萬惡之首都在: 指標 (POINTER)。
因為有 pointer,因此 C 絕對不能 自動 幫你調整記憶體位置,也就一定會有這種問題。看到為何我在上篇提到的程式碼要把 pointer 的值印出來? 因為這代表我可以輕易拿的到實際的位址,因此任何重新定址 (relocation) 的動作一定會影響到程式的執行。所以最根本的解決辦法就是把 pointer 這東西拿掉。
年紀較輕的程式語言,如我常提到的 Java 跟 C#,都完完全全的把 pointer 從語言內移掉了,只留下 reference 這類差不多的東西。除了拿不到絕對的 address 之外,其它功能一個都不缺。但是這樣帶來的好處是很明顯的,除了一般書上講到爛的理由: “更安全,更簡易” 之外,很重要的一點就是,像 CLR or JavaVM 這種環境,開始有機會去搬移記憶體配置的區塊,徹底的由系統層面解決這種問題了。
.NET / Java 回收記憶體的動作是自動的,就是常聽到的 Garbage Collection,而上面提到的 relocation,就是指在回收時順便把剩下已配置的空間排在一起,搬移記憶體區塊所需要的重新定址動作。這種類型的 GC 有個特別的名辭,叫作 compact collection。理論上,.NET 已經具備這樣的條件了,應該要有能力可以解決這樣的問題。
不過 “可以解決” 跟 “已經解決” 仍然有一段差距,那麼現在的 .NET CLR 到底行不行? 一樣的範例程式用 C# 改寫一下,同樣的試看看,不過這次懶的再放好幾種版本試試看了,除了最大可用記憶體可能有差別之外,其它應該都統一了。我只針對 .NET 2.0 (x86) 一種版本測試,一樣,鐵齒的讀者們,有興趣就抓回去試一試…。
整段程式碼跟之前 C 版本大同小異,就是照順序配置 64mb 的 byte[],直到丟出 OutOfMemoryException,然後跳著釋放,接著再配置 72mb 的 byte[],看看能不能配置成功? 直到再丟出 OutOfMemoryException 為止,能配置多少記憶體? 這邊為了方便,我直接在 vista x86 系統上測試:
測試的結果令我想殺人,竟然是 FAIL ? 放掉的空間拿不回來…
後來想到,程式移除 reference,不見得會立刻釋放記憶體,總得等垃圾車 (Garbage Collect) 來收拾一下… 手動呼叫了 GC,也強迫指定要回收所有的 Generation 了 (呼叫: GC.Collect(GC.MaxGeneraion) ) 再試一次:
結果好不到那裡去,難到我沒用市政府的垃圾袋嘛? [:@] 查了一下 MSDN,常見的 generation 問題也試過,沒有用。90% 講 CLR GC 的問題都在探討 generation 的問題… 查到某 Java 名人的 文章,提到了 compact collection 比較接近,不過沒有講怎麼明確的啟動這樣的 GC 啊… 後來去翻 .NET runtime 裡關於 garbage collection 的設定,發現還有這玩意… gcConcurrent / gcServer:
gcConcurrent: Specifies whether the common language runtime runs garbage collection on a separate thread.
gcServer: Specifies whether the common language runtime runs server garbage collection.
講的很清楚,不過對我沒啥用。gcConcurrent可能的影響是,也許呼叫後系統還在GC,我的程式就先跑下去了? 因此這東西關掉也許有幫助,再來試一次:
真慘,一點幫助都沒有… 放掉的 768MB,只撈回 72MB,再來看一下最後一個 gcServer,看它的 HELP 看不大出來什麼是 “server garbage collection” ? 算了,試一下比較快:
Bingo,看來這個參數下下去才是我預期的結果,放掉了 576MB,後面撈了 648MB 回來。這樣的作法,已經完全不會受到 memory fragment 問題的影響,証實了 compact collection 是有發恢它的效用的,只不過這個參數實際的作用,翻遍了 Google / MSDN,得到的都是很模菱兩可的答案,不外乎是你的程式如果是 blah blah blah 的話就要用 gcServer,這樣會比較好之類的,不過實際的差別則看不大出來。沒有任何一篇文件明確提到 server gc 會做 compact collection (如果這篇不算的話,哈哈),而 workstation gc 不會,也許前面的方式也會觸發 compact collection也說不定,只是時機不成熟…
抱著不可能的希望,用 Reflector追看看,果然不出所料,Reflector也看不到細節,因為全都呼叫 native code 去了。不過這次的測試,至少確定了,在啟用 gcServer option 之後,CLR 的 GC 是會進行 compact collection 的。
寫到這裡,本系列文章結束,只是為了在新的平台驗證古早的問題而以,果然時代在進步,以前耽心的問題現在都不再是問題了。這一連串試下來,學到了一課,原來 gcServer 有這個差別,算是值回票價了。最後把我的測試程式碼貼一下,一樣,歡迎拿去各種平台試一下,有不一樣的結果也記得通知我一聲!
[Program.cs]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ClrMemMgmt
{
class Program
{
static void Main(string[] args) {
List<byte[]> buffer1 = new List<byte[]>();
List<byte[]> buffer2 = new List<byte[]>();
List<byte[]> buffer3 = new List<byte[]>();
//
// allocate
//
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("1. Allocate 64mb block(s) as more as possible...");
try
{
while (true)
{
buffer1.Add(new byte[64 * 1024 * 1024]);
Console.Write("#");
buffer2.Add(new byte[64 * 1024 * 1024]);
Console.Write("#");
}
}
catch (OutOfMemoryException)
{
}
Console.WriteLine();
Console.WriteLine(" Total {0} blocks were allocated ( {1} MB).", (buffer1.Count + buffer2.Count), (buffer1.Count + buffer2.Count) * 64);
//
// free
//
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("2. Free Blocks...");
buffer2.Clear();
Console.WriteLine(" Total: {0} blocks ({1} MB)", buffer1.Count, buffer1.Count * 64);
//
// GC
//
GC.Collect(GC.MaxGeneration);
//
// allocate
//
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("3. Allocate 72mb block(s) as more as possible...");
try
{
while (true)
{
buffer3.Add(new byte[72 * 1024 * 1024]);
Console.Write("#");
}
}
catch (OutOfMemoryException)
{
}
Console.WriteLine();
Console.WriteLine(" Total: 64mb x {0}, 72mb x {1} blocks allocated( {2} MB).\n", buffer1.Count, buffer3.Count, buffer1.Count * 64 + buffer3.Count * 72);
Console.ReadLine();
}
}
}
[configuration file]
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<!--<gcConcurrent enabled="false" />-->
<!--<gcServer enabled="true" />-->
</runtime>
</configuration>
該來揭曉謎底了,在人氣不怎麼高的地方拋這些冷門的問題,看的我都覺的 “這版主真是不自量力” .. 咳咳.. 為了把之前的心得,在現在的主流作業系統及平台再驗證一次,只好自己花了點小工夫,把 C 挖出來寫個測試程式,不過 C / C++ 實在太久沒寫了,已經忘到連語法都得翻 HELP 的程度 :~ 花了些時間才搞定。
不過也因為這樣,連帶的查了一下如何編譯 x64 的程式碼,還有一些相關的設定項目。這次測試只測了 windows 的環境,沒辦法,這把年紀實在沒力氣再去摸第二種 OS 了,如果有善心人事要幫我測的話,我倒是很樂意把你測的結果一起放上來! 程式碼請看 [註3]
不多說廢話,測試主要會針對三種環境來測試:
原本還想加上 /3GB options 來測的,不過這樣跟 (2) 幾乎是一樣的,只差在 3GB 跟 4GB 的可用空間而以,差異不大,當然就省下來了 [H]
測試結果就直接抓畫面附在底下了。程式碼當然都是同一份。原始 project 放在 [這裡]。其實這次問題的關鍵,跟 windows / linux,32/64,都沒有直接關係,唯一有關的是,你的 memory address space 到底有沒有用完… 怎麼說? 先來看結果:
看不出個所以然? 簡單畫個表格整理一下測試結果:
測試環境統一為 Windows 2003 x64 版本,可用記憶體為 2GB,分頁檔有 4GB。
測試項目 | 測試 #01 | 測試 #02 | 測試 #03 |
---|---|---|---|
執行環境 | 32Bits (WOW64) | 32Bits (WOW64) | 64Bits (Native) |
Build Options | x86 | x86 + LargeAddressAware | x64 |
可定址空間 / 實際可用空間 | 2048MB / 1920MB | 4096MB / 3904MB | 8TB / 4032MB |
問題的測試結果 / 可以配置的 72mb 區塊 | NO / 2 | NO / 2 | YES / 27 |
這結果大概會跌破一堆人的眼鏡,雖然板上回應的很冷清,不過私下 MSN 問了幾個人,有很肯定的,也有不確定的,當然也有亂猜猜的很肯定的 (S*M,就是你…),不過答案都很兩極,不是都不行,不然就是都可以…
就理論上來說,分頁的記憶體管理方式,的確是不能解決在 virtual memory address space 過度分散 (fragment) 的問題,如果程式或作業系統沒有作適度的 defrag,那麼你要挖一塊記憶體的要求一定會失敗,但是為什麼測試 #03 會通過? 因為實際可用的 Memory ( Physical RAM + Swap File ) 也不過 6GB,你的程式需索無度,要求超過的怎麼樣也生不出來。6GB 扣掉其它 OS / AP 用掉的,跟理論上能用的 8TB 實在是差太多,造成你的 virtual memory address space 跟本不可能用完。當然 不可能 這字眼別用太滿,十年前也是認為 4GB 跟本不可能用完,不過我現在的 PC 就已經裝了 6GB … [:$] 上表中列了一些較特別的參數,像是明明是 32 位元,怎麼可定址空間是 2048MB ? 還有 LargeAddressAware 是什麼? 這些屬 windows 較特別的規矩,我在文章最後面的 [註1] 說明一下。
現在的 PC,隨便都是 1GB / 2GB RAM,加上分頁檔,超過 4GB 跟本是件很普通的事,意思是寫不好的程式,的確是已經會面臨到這樣的困境了,明明記憶體還夠用,但是系統卻回報 Out Of Memory …。只可惜這樣的問題,OS一點忙都幫不上。因為 OS 沒有辦法替你做 Memory Defragment 的動作 [註2]。上一篇 我有畫張記憶體配置的圖,只能用來說明 #01 / #02 的情況,換成 #03 就不大適用了。只要可定址空間夠大,基本上你只需要考慮你配置的記憶體總量有沒有超過可用的記憶體就好,是不大需要在意是不是有 fragment 的問題,除非你的可配置空間跟可用空間很接近,這問題才會浮現出來。就像積木買來,積木的體積跟盒子差不多大,你要全擺進去就得花點工夫安排一下,否則一定會有一些是收不進去的一樣 (幫吳小皮收玩具的感想… -_-)。不過如果你換一個大盒子,或是把整個房間當成大盒子來看,隨便丟都沒問題,連會不會撞在一起都不用耽心,一定不會有空間夠卻放不進去的問題,這就是差別。
這測試結果看起來很可怕,感覺起來只要是 32 位元的程式,加上是 server or services,會長時間運作的,好像都有可能碰到這種問題。這不算是 Memory Leak (因為記憶體是真的有成功的被釋放),那麼 Java / .NET 宣稱的 Garbage Collection 回收機制到底會不會碰到這個問題? 別耽心,等著看下一篇就知道了 XD
–
註 1. /LARGEADDRESSAWARE:
32 位元系統可定址空間應該是 2^32 = 4GB 沒錯,不過 Microsoft 把它一分為二,規劃一半是給 Kernal,另一半才是給 APP 使用。意思是你的程式再怎麼用只能用到 2GB。不過這種問題幾年前就浮現出來了,Microsoft 特地在 OS 上加了 /3GB 這種參數,可以把 OS : AP = 2GB : 2GB 的規劃,調整為 1GB : 3GB。不過相對的程式在編譯時也要加上 /LARGEADDRESSAWARE 參數,才有可能讓 AP 取得 2GB 以上的記憶體。
所以即使在 x64 下執行的 x86 應用程式,跟本沒有 OS 那 2GB 的需求,一般 x86 APP 在 64 位元作業系統下仍然只能用到 2GB,但是不同參數編譯出來的程式碼,就能用到 4GB (如果是在加上 /3GB 的 x86 OS,則大約是 3GB)
註 2. 為什麼 OS 不能替 Memory 執行 Defragment 的動作?
原因很簡單,測試的程式是用 C / C++ 這類可以直接存取 pointer 的程式語言寫的。試想一下 OS 如果替你把已配置的記憶體區塊挪一挪會發生什麼事? 你拿到的 pointer 全都成了廢物,因為它指向的記憶體,可能已經不是當時你認識的資料了… 因為資料有可能被強迫搬家,你的通訊錄再也沒有用,老朋友就失聯了…
因此,別的程式語言不一定,但是允許你直接使用 pointer 的語言,這類的問題你閃不掉,一定得自己想辦法…
註 3. 測試程式碼
這段 code 我做了點改變,因為 4kb block size 實在太小了 (相對於 4GB 上限),印出訊息一大堆,執行又慢,因此我自己把問題的參數調整一下,把問題的 4kb 改成 64mb,而最後要配置的 5kb 則改成 72mb。若對這段 code 有興趣的人,可以直接 copy 去用。我直接把 source code 貼在底下 ( C++ 語法忘了一大半 :P,因此用的都是單純的 C … 除了 conio.h 之外,應該隨便都能成功編譯吧,看誰有興趣可以拿到 Linux 下去試看看….):
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <malloc.h>
void init_buffer(void *ptr, unsigned int size) {
if (ptr == NULL) return;
//for (int count = 0; count < size / sizeof(long); count++) {
// ((long *)ptr)[count] = 0;
//}
}
int main(const int& args) {
void *buffer1[4096];
void *buffer2[4096];
void *buffer3[4096];
//
// allocate
//
printf("\n\n");
printf("1. Allocate 64mb block(s) as more as possible...\n");
int total = 0;
for (int count = 0; count < 4096; count++) {
buffer1[count] = buffer2[count] = NULL;
buffer1[count] = malloc(64 * 1024 * 1024);
if (buffer1[count] == NULL) break;
init_buffer(buffer1[count], 64 * 1024 * 1024);
printf("#");
total++;
buffer2[count] = malloc(64 * 1024 * 1024);
if (buffer2[count] == NULL) break;
init_buffer(buffer2[count], 64 * 1024 * 1024);
printf("#");
total++;
}
printf("\n Total %d blocks were allocated ( %d MB).\n", total, total * 64);
//
// free
//
printf("\n\n");
printf("2. Free Blocks...\n");
for (int count = 0; count < 4096; count++) {
if (buffer2[count] == NULL) break;
free(buffer2[count]);
buffer2[count] = NULL;
total--;
printf("#");
}
printf("\n Total: %d blocks (%d MB)\n", total, total * 64);
//
// allocate
//
printf("\n\n");
printf("3. Allocate 72mb block(s) as more as possible...\n");
int total2 = 0;
for (int count = 0; count < 4096; count++) {
buffer3[count] = malloc(72 * 1024 * 1024);
if (buffer3[count] == NULL) break;
printf("#");
total2++;
}
printf("\n Total: 64mb x %d, 72mb x %d blocks allocated( %d MB).\n", total, total2, total * 64 + total2 * 72);
printf("\nDump Blocks Address:\n");
for (int count = 0; count < 4096; count++) {
if (buffer1[count] == NULL) break;
printf(" 64mb block ponter: [%08p] ~ [%08p] \n", buffer1[count], (long)buffer1[count] + 64 * 1024 * 1024);
}
for (int count = 0; count < 4096; count++) {
if (buffer3[count] == NULL) break;
printf(" 72mb block ponter: [%08p] ~ [%08p] \n", buffer3[count], (long)buffer3[count] + 72 * 1024 * 1024);
}
_getch();
return 0;
}