如何优化 FineUI 控件库的性能,减小 80% 的数据上传量!

不论是第一次使用 FineUI 控件库的网友,仍是有着 3 年以上使用经验的网友,都对 FineUI 的简单印象深入。固然,“一切为了简单”也是 FineUI 一句响亮的口号,不只如此,一个开源项目要想立足并长久发展下去,光凭着简单是不行,还要有本身的特点,这个特点就是创新。 php

 

从 FineUI 的官网(http://fineui.com/)咱们明显看到 FineUI 的三个大特性“一切为了简单”、“用心实现80%的功能”、“创新因此独一无二”: html

image

 

而今天要讲的就是“创新”的范畴。 浏览器

 

FineUI 的 XState 机制

咱们都知道 ViewState 是 ASP.NET WebForms 的一个重要的基础,用来在页面回发过程当中维护控件的服务器端状态,这样咱们就能方便的在回发事件处理函数中随意使用控件属性了,好比下面代码: 服务器

 1: Label1.Text = TextBox1.Text;

可是在 AJAX 的应用环境中,ViewState 会代码下载数据的冗余,因此 FineUI 很早就在其内部实现了适合 AJAX 的 XState 机制,从而减小下载数据的冗余,加快页面的呈现速度。 网络

 

可是请注意,FineUI 使用 XState 并不是启用 ViewState,传统的 ASP.NET 控件仍然使用 ViewState,你们也仍然能够在 ViewState 中保存少许的数据,好比以下代码依然有效: ide

 1: if (ViewState["BindGrid1"] != null && Convert.ToBoolean(ViewState["BindGrid1"]))
 2:  {
 3:  BindGrid();
 4:  ViewState["BindGrid1"] = false;
 5: }

 

以前有一篇详细的文章来对比 ViewState 和 XState 对使用 FineUI 控件库的印象:http://www.cnblogs.com/sanshi/archive/2013/01/08/2850459.html 函数

 

这里咱们简单把结论列一下: 性能

对于以下这个页面,咱们分别比较页面第一次加载和点击按钮回发两个过程当中下载的数据量: 测试

extaspnet_example_1

 

使用 ViewState 的版本: 第一次页面加载下载数据(5068 bytes)     点击按钮回发下载数据(1251 bytes) 优化

使用 XState 的版本:       第一次页面加载下载数据(4922 bytes)     点击按钮回发下载数据(709 bytes)

 

可见,XState 带来了一个很大的优点,那就是减小数据的下载量。

 

如今,开始关心数据上传量

以前一直很关心数据下载量,而忽视了数据上传量。其实咱们所处的网络环境是一个上行下行不对称的网络,通常上行速度是下行速度的 1/2,甚至更少。来引用一篇文章中的一段:

Why Upload and Download Speeds Differ

  • Upload speed is usually slower than download speed because Internet providers have designed their systems to optimize download speeds. This is because most Internet users spend more time downloading than uploading. In other words, Internet providers give priority to downloading since it's more frequently done than uploading.

换句话说,由于人们对下载的需求更旺盛,因此网络提供商通常会分配给下载更高的传输速度。

 

我一直没有向这个方向考虑,直到有论坛网友提出了这个问题:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3166

image

 

看到这篇帖子的时候,我就意识到了,FineUI 是作出改变的时候了!

 

由于咱们须要在服务器和客户端以前持久化数据,对于这个网友的例子而言,就是表格中那些已经渲染后的HTML代码。通用的办法就是对客户端提交的数据进行压缩,可是在客户端使用 JavaScript 实现相似 Gzip 的压缩简直就是天方夜谭(浏览器性能受不了)!

 

怎么办?

 

均衡下载量和上传量

既然没法在客户端进行压缩,就只好在服务器端进行压缩了。因此思路以下:

1. 在页面加载时在服务器端压缩须要持久化的状态,并写入页面中;

2. 下载 AJAX 提交时,提交压缩后的持久化数据;

3. 在 AJAX 提交过程当中,从新生成新的压缩后的持久化数据,并写入页面中;

4. 如此下去….

 

基于以下几点考虑,这部分新增的压缩数据不会对下载量形成很大的影响:

1. 下行带宽通常都比较充足;

2. 通过压缩后的持久化数据通常比较小;

3. 下载数据通常都会通过 GZIP 压缩。

 

具体到 FineUI 中的实现,也很简单,首先定义 GZIP 压缩和解压函数:

 1: public static string Gzipped(string source)
 2: {
 3:  using (var outStream = new MemoryStream())
 4:  {
 5:  using (var gzipStream = new GZipStream(outStream, CompressionMode.Compress))
 6:  {
 7:  using (var mStream = new MemoryStream(Encoding.UTF8.GetBytes(source)))
 8:  {
 9:  mStream.WriteTo(gzipStream);
 10:  }
 11:  }
 12:  
 13:  return StringUtil.EncodeTo64(outStream.ToArray());
 14:  }
 15: }
 16:  
 17: public static string Ungzipped(string source)
 18: {
 19:  byte[] bytes = Convert.FromBase64String(source);
 20:  
 21:  using (GZipStream stream = new GZipStream(new MemoryStream(bytes), CompressionMode.Decompress))
 22:  {
 23:  const int size = 512;
 24:  byte[] buffer = new byte[size];
 25:  using (MemoryStream memory = new MemoryStream())
 26:  {
 27:  int count = 0;
 28:  do
 29:  {
 30:  count = stream.Read(buffer, 0, size);
 31:  if (count > 0)
 32:  {
 33:  memory.Write(buffer, 0, count);
 34:  }
 35:  } while (count > 0);
 36:  
 37:  return System.Text.Encoding.UTF8.GetString(memory.ToArray());
 38:  }
 39:  }
 40: }

而后为控件基类添加一个须要对哪些属性进行 GZIP 压缩的属性:

 1: private List<string> _gzippedAjaxProperties = new List<string>();
 2:  
 3: internal List<string> GzippedAjaxProperties
 4: {
 5:  get { return _gzippedAjaxProperties; }
 6:  set { _gzippedAjaxProperties = value; }
 7: }

最后,在页面第一次加载和 AJAX 过程当中压缩这个属性:

 1: bool propertyGzippped = _gzippedAjaxProperties.Contains(property);
 2: string propertyGzippedValue = String.Empty;
 3:  
 4: object propertyValue = GetPropertyJSONValue(property);
 5:  
 6: JToken tokenValue = propertyValue as JToken;
 7: jo.Add(property, tokenValue);
 8:  
 9: if (propertyGzippped)
 10: {
 11:  propertyGzippedValue = tokenValue.ToString(Newtonsoft.Json.Formatting.None);
 12: }
 13:  
 14: if (propertyGzippped && !String.IsNullOrEmpty(propertyGzippedValue))
 15: {
 16:  jo.Add(property + "_GZ", StringUtil.Gzipped( propertyGzippedValue));
 17: }

 

测试优化结果

为了进行测试,咱们建立了一个表格页面,页面效果以下:

snap254

 

这个页面总共有 22 行数据,咱们来比较页面第一次加载和点击“选中了哪些行”两个操做在优化先后的结果。

 

优化前,页面第一次加载(UP:0   DOWN:30684):

image

 

优化前,页面回发(UP:33202   DOWN:186):

snap256

 

优化后,页面第一次加载(UP:0   DOWN:34366):

snap257

 

优化后,页面回发(UP:6016   DOWN:186):

snap258

 

通过优化后,页面第一次加载时下载数据由 30684 bytes 增长为 34366 bytes,增长了 12%,而这个增长量不会对下载形成多大的影响。

而上传量却有大幅减小,AJAX 回发时上传数据由原来的 33202 bytes 减小为 6016,减小了 82%,对于上行带宽不足的现实,这个改变带来的影响确实巨大的!

 

结论

优化后的 FineUI 控件库会稍微增长下载数据量,可是考虑到下行带宽通常比较充裕,而且下行数据通常会通过 GZIP 压缩,因此这个改变影响不大。现实的好处是上传数据大幅减小了 80%,这个改变带来的影响确实巨大的!

 

 

注:这个特性会加 FineUI 下个版本中去(FineUI v3.3.1)。