内容目录
5.3 启动时数据的加载和predication数据的从新组织 16
1 PrefetchDNS
1.1 prefetchDNS干了什么
这个函数只是触发一个prefetchDNS的意愿,具体操作下层已经被封装好了。
EarlyBird::prefetchDNS
-〉WebCore::prefetchDNS,这个定义在core/platform/network/DNS.cpp中
-〉WebKit::Platform::current()->prescientNetworking().这其实是调用了:
/content/renderer/renderer_webkitplatformsupport_impl.cc:303:RendererWebKitPlatformSupportImpl::prescientNetworking()的prefetchDNS函数。RendererWebKitPlatformSupportImpl类实际上是render这边的platform实现类。放到了content模块里面,可见content模块不光是对外提供接口,适配webkit,也对webkit提供平台相关的实现。
如何实现DNS预取?
getContentClient()->render()是Content_render_client.cc中
getContentClient()->browser()是Content_browser_client.cc中。
而classAwContentRendererClient : public content::ContentRendererClient{是android_webview下的实现。但是AwContentRenderClient的GetPrescientNetworking函数是空的。所以,实际上好像用的是:
WebKit::WebPrescientNetworking*
ChromeContentRendererClient::GetPrescientNetworking() {
return prescient_networking_dispatcher_.get(); }其中PrescientNetworkingDispatcher是关键。
class PrescientNetworkingDispatcher: public WebKit::WebPrescientNetworking。定义与dolphin_blink/chrome/ render/net下。这个类实现了prefetchDNS和preconnect.
void PrescientNetworkingDispatcher::prefetchDNS(
const WebKit::WebString& hostname) {
if (hostname.isEmpty())
return;
std::string hostname_utf8 = UTF16ToUTF8(hostname);
net_predictor_.Resolve(hostname_utf8.data(),hostname_utf8.length());
}
Net_predictor是RendererNetPredictor。
void RendererNetPredictor::DnsPrefetchNames(size_t max_count) {
…
RenderThread::Get()->Send(newChromeViewHostMsg_DnsPrefetch(names));} //本质上发送消息给browser处理。
.dolphin_blink/chrome/browser/renderer_host/chrome_render_message_filter.cc:95:
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DnsPrefetch, OnDnsPrefetch),可见browser部分的render_host模块里处理了这个消息。
实际工作由/chorme/browser/net/Predictor完成:
void Predictor::DnsPrefetchList(const NameList& hostnames),任务会被分配的browser的io线程的queue里面。
->ResolveList()
->AppendToResolutionQueue().
之后LookupRequest*request = new LookupRequest(this, host_resolver_, url);
Request.start()
->HostResolver::resolve()完成之后调用LookupRequest::OnLookupFinished()。
HostResolve::resolve干了什么:
HostResolverImpl::Resolve(),创建一个job.然后加入到jobs队列里面。Job.schedule()把Job加入dispather.
class HostResolverImpl::Job : public PrioritizedDispatcher::Job,
先尝试从hosts列表里面找如果有就不用从网络上找了,ServeFromHosts就是干这个的。
此外,会启动job:
Job::start()
->StartDnsTask()会创建一个DNSTask并start它。
Class HostResolverImpl::DnsTask.
DNSTask.start()会创建一个DnsTransaction,并start这个transaction.Transaction会创建DnsAttempt,或者是DnsUDPAttempt或者是DnsTCPAttempt,并start这些attempt.他们有个状态机,有个DoLoop(),在loop里面会使用socket发送数据接受数据等。Response来了会把数据读出来,然后调用DnsResponse::InitParse来解析。完成后,各种callback,回到OnTransactionComplete(),他会解析response,得到addresslist:
DnsResponse::Result result =response->ParseToAddressList(&addr_list, &ttl);
之后可能会有个排序,然后调用transaction的OnSuccess(),它调用callback_.Run(),传递接过出去。然后Job::OnDnsTaskComplete()调用HostResolverImpl::OnDnsTaskResolve()通知完成也会调用Job::CompleteRequests()
->resolver_->dispatcher_.OnJobFinished();通知dispatcher一个job完成了可以启动别的job了。
resolver_->CacheResult(key_,entry, ttl);//通过resolver保存到HostCache里面,HostCache里面维护了一个hostname到addrlist的映射map.
req->OnComplete(entry.error, entry.addrlist);
那么HostCache究竟保存在哪里?被谁维护?创建HostResolver时传入了HostCache,但是是谁创建的HostResolver?
IoThread里面有个结构体Globals,里面维护了HostResolver的ref引用。保证它不被释放。在IoThread的initAsync里面会创建这些iothread的数据,包括hostresolver.同时HostCache也被创建,并被传引用给使用者。
HostResolver::CreateSystemResolver(const Options& options,NetLog* net_log) {
scoped_ptr<HostCache> cache;
if (options.enable_caching)
cache =HostCache::CreateDefaultCache();
return scoped_ptr<HostResolver>(newHostResolverImpl(
cache.Pass(),
GetDispatcherLimits(options),
HostResolverImpl::ProcTaskParams(NULL,options.max_retry_attempts),
net_log));
}
1.2 哪些地方调用了prefetchDns
原有的调用点:
-
Chrome::mouseDidMoveOverElement(,鼠标路过一个连接时
-
HTMLAnchorElement::parseAttribute,解析文档的anchor元素时
-
HTMLLinkElement.cpp中的HTMLLinkElement::parseAttribute()调用HTMLLinkElement::process()调用了LinkStyle::process()调用了LinkLoader::loadLink(),而LoadLink调用了prefetchDns。解析link元素的内容时。
新加入的EarlyBird::prefetchDns调用点:
-
HTMLResourcePreloader::preload(),在HTMLParser解析html的空闲时,比如等待script时会启动一个preloadscanner来scan网页内容看有没有需要预先加载的资源,如果有就会加载该资源,加载的方法是:loadingDocument->fetcher()->preload()可见也是会走到resourceFetcher()的,不过走的是preload。如果取消这里预取DNS的操作,转而只在ResourceFetcher::loadResource()那里预取可以么?看代码里面发现,这一步最终也会走到ResourceFetcher::requestResource里,继而又可能会走到ResourceFetcher::loadResource那里,所以貌似这一步可以省略掉,即可以合并到ResourceFetcher::loadResource里。在加载之前,可以预取一下dns.这个调用点的调用场景有如下几种:
调用点1:HTMLDocumentParser::pumpTokenizer处理每个token时,并且是在waitforscript时才会scan来看是否有需要preload的资源。
->HTMLPreloadScanner::scan(HTMLResourcePreloader* preloader,const KURL& startingBaseElementURL)
->HTMLResourcePreloader::takeAndPreload()
->HTMLResourcePreloader::preload(PassOwnPtr<PreloadRequest>preload)
->earlyBird::prefetchDns
调用点2:此外,HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser()也是在waitforscript时
->HTMLResourcePreloader::takeAndPreload()
HTMLDocumentParser里面有很多处调用preloader的地方,类似的都是在parser空闲,等待script时才触发preloader的scan操作来看是否有需要preload的资源.问题:preloadscanner究竟scan什么东西?那些东西是被认为是可以预取的?HTMLPreloadscanner通过TokenPreloadScanner来扫描token,获取到可以预先加载的资源信息,然后根据这些信息构造出preloadrequest,让preloader去预先加载。在TokenPreloadScanner::StartTagScanner::processAttribute()函数中可以看到,当遇到script或者img节点时,这些节点都是需要被加载的,当解析他们的attributes时,如果遇到src属性,就把其属性值作为一个需要预取的资源地址。记录下来(调用setUrlToLoad),当后来要构造Preloadrequest时,就可以用这里记录的地址了:TokenPreloadScanner::StartTagScanner::createPreloadRequest函数就是用这个地址创建preloadrequest的。TokenPreloadScanner::scan其实就是通过扫描token然后创建preloadrequest的。而TokenPreloadScaner::scan是被HTMLPreloadScanner::scan调用的。拿到了request之后,就可以用HTMLResourcePreloader::takeAndPreload(PreloadRequestStream&r)来处理这些request了。这里的stream其实就是一个request构成的vector。循环加载其中的request:HTMLResourcePreloader::preload(PassOwnPtr<PreloadRequest>preload), 这个函数就调用Document里面的ResourceFetcher的preload函数来加载这个request.可以看出这里加载的内容其实是在拿到了主网页,并解析的过程中,预加载其中包含的子资源。
-
DocumentLoader::startLoadingMainResource(), 加载主网页时,提前预取dns.并且依据oracle里面的资源数据,预取预测的资源的dns.如果加载主网页时现在不预取的话什么时候获取dns?其实,估计加载主resource,也会走到resourceFetcher里面,所以预取DNS的操作我觉得也可以合并到ResourceFetcher::loadResource里面,当然,如果提前的话更好。
-
ResourceFetcher::loadResource() 预取真正要加载的资源的dns。
目前可以看到针对preloader和mainresource的加载时机和ResourceFetcher::loadResource有些重复,但是其实是把prefetchDns的操作提前了一些。对于系统本来就要预加载的资源,可以在决定要预加载那些资源的时刻就立刻启动dns预取。在决定要加载主资源时,也立刻预取dns,比推迟到ResourceFetcher::loadResource要好。对于一些并没有预加载的资源,就只能推迟到ResourceFetcher::loadResource那里了。
如果不预取的话,会是什么时候获取的?可以在HostResolver的相关函数里面加一个断点看Callstack。
如果不prefetchDns的话,Document的ResourceFetcher::requestResource()->resourceFetcher::load()->ResourceLoader::create()每个resourcerequest创建一个resourceLoader->ResourceLoader::start()->WebURLLoader::loadAsynchronously()
WebURLLoader的client是ResourceLoader。WebURLLoaderImpl::Context是context.
->WebURLLoaderImpl::Context::start()
->创建ResourceLoaderBridge,然后ResourceLoaderBridge::start
下面具体可以参看打开tab的流程
1.3 flushPendingDNS
暂时没有被用到
1.4 DNSCacheDb
作用:目前只是避免重复prefetchDNS操作
维护了一个hashset的被resolved的hostname
还维护了一个pendinghost将一些resolve操作缓存起来以后一起提交,但是这个机制目前没有生效,是否可以去掉?
2 PreConnect
2.1 preconnect作了什么
和prefetchDns类似,处理的逻辑在PrescientNetworkingDispatcher里。也是发送消息给browser.
void PrescientNetworkingDispatcher::preconnect(
const WebKit::WebURL& url,
WebKit::WebPreconnectMotivation motivation) {
if (isPreconnectEnabledForMotivation(motivation))
content::RenderThread::Get()->Send(newChromeViewHostMsg_Preconnect(url));
}
在browser这边:dolphin_blink/chrome/browser/render_host/chrome_render_message_filter.cc文件中有:ChromeRenderMessageFilter::OnMessageReceived:
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_Preconnect, OnPreconnect),
和prefetchDns类似,具体操作也是由实际工作由/chorme/browser/net/Predictor完成:
-〉Predictor::PreconnectUrl()
->dolphin_blink/chrome/browser/net/preconnect.cc中的PreconnectOnIOThread()
->HttpStreamFactoryImpl::PreconnectStreams
->intHttpStreamFactoryImpl::Job::Preconnect(int num_streams)
->intHttpStreamFactoryImpl::Job::StartInternal() {
一个状态机,启动
-〉intHttpStreamFactoryImpl::Job::RunLoop(int result) {
-〉intHttpStreamFactoryImpl::Job::DoLoop(int result) {
→ 继续看,以后网络连接这块可以再深入看看。
2.2 哪些地方调用了preconnect
2.2.1 浏览器原本调用preconnect的地方
HTMLAnchorElement::PrefetchEventHandler::prefetch(), 这个函数是处理anchorelement时作出预先连接。实际上这个prefetch()的调用点有:
-
HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event*event)
鼠标over一个anchorelement上面的事件发生时
-
voidHTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event*event)
鼠标down在一个anchorelement时
-
HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event*event)
手势动作
-
HTMLAnchorElement::PrefetchEventHandler::handleGestureTapDown(Event*event)
手势down
2.2.2 earlybird新加入的调用点
-
DocumentLoader::startLoadingMainResource()
加载主资源时,预先连接
从plo中找出潜在的子资源的地址,预先连接
-
voidHTMLResourcePreloader::preload(PassOwnPtr<PreloadRequest>preload)
预加载子资源时,预先连接。
-
ResourceFetcher::loadResource
正式加载子资源时,提前建立连接。
2.3 ConnectionCache
HashMap<void*, HostCountMap*>m_preconnectedHostPortPair;
一个Doc*对应一个map,map里面是key和链接数的映射,key是域名和端口组成的字符串.
这个结构用来记录链接的域名,端口,在开启一个新的链接前检查一个文档对于一个域名的连接数,不能超过限制。
Hittestconnection并没有使用。可以去掉?
3 资源预测加载
3.1 做了什么
根据网页的url找出预测的资源的url,提前加载资源。
DocumentLoader::startLoadingSpeculativeResource()
->Downloader::prefetchResource()//针对每个资源
->ResourceFetcher::requestSpeculativeResource()
->ResourceFetcher::requestResource
3.2 调用点
基本上所有准备加载主资源的地方都要加载预测资源:
-
FrameLoader::init()
-
FrameLoader::loadWithNavigationAction()
4 Java层的调用者已经没用了
Java层有一些PLO相关代码:
DolphinBrowserEn下:
./src/mobi/mgeek/TunnyBrowser/BrowserActivity.java:1911: NetworkPLOFactory.getInstance().updatePloData();
DolphinCoreLibrary下:
./src/com/dolphin/browser/dolphinwebkit/DolphinNetworkPLO.java:32: * @see com.dolphin.browser.core.INetworkPLO#updatePloData()
./src/com/dolphin/browser/dolphinwebkit/DolphinNetworkPLO.java:35: public void updatePloData() {
./src/com/dolphin/browser/dolphinwebkit/DolphinNetworkPLO.java:36: sCoreInstance.updatePloData();
./src/com/dolphin/browser/core/NullNetworkPLO.java:24: publicvoid updatePloData() {
./src/com/dolphin/browser/core/INetworkPLO.java:11: public voidupdatePloData();
dolphinwebview下:
./src/dolphin/webkit/PLO.java:42: public final void updatePloData() {
./src/dolphin/webkit/PLO.java:44: JniUtil.updatePloData(); //
./src/dolphin/webkit/WebkitCallback.java:21: // TODO: when most jetpack are upgraded, we could moveupdatePloData()
./src/dolphin/webkit/WebkitCallback.java:23: // PLO.getInstance().updatePloData();
./src/dolphin/webkit/JniUtil.java:271: public static synchronized void updatePloData() {
上述调用jni的updatePloData接口,native实现在PageLoadOracle.cpp中的voidjniUtil_updatePloData()。是为了从java层调用plo的保存操作。现在已经不再使用了,是否可以去掉?或者依然保持对shell的接口?
5 Db里的数据
Resourcefile: r.plo
recordFile: p.plo
resource文件格式:
loop()
{
urllen 1byte int
url urllen bytes 字串没有尾巴0
charsetlen 1 byte int
charset charsetlen bytes 字串,没有尾巴0
lastvisittime 4bytes int
resourceType 1 byte enum
}
record文件格式:
loop()
{
webpageurllen 1byte int
webpageurl webpageurllen bytes, 字符串
lastvisittime 4bytes int
resource_cnt 1 byte int
resourceids resource_cnt resource ids, int
}
5.1 模型数据的获取
数据怎么来的?
每在一个网页上加载了一个资源就调用:
void PageLoadHistoryDb::reportPageLoadResource(const KURL&webPageUrl, const KURL& resourceUrl, String resourceCharset,PageLoadResourceType resourceType)
webPageUrl是document->baseUrl().
ResourceUrl就是资源url,是resourceFetcher::requestResource()里需要加载的资源url.
这个函数做如下几件事:
-
记录resource
记录下resource的url, charset, type, lastvisit time, 生成一个resourceid.、
db::m_resource保存所有的resource,是resource的 vector.
db::m_resourceUrl2IdMap是一个resourceurl => resource id的映射
-
生成record
一个record和一个网页document的baseurl关联。描述了一个网页的资源加载情况
如果record已经有了,使用已有的
db::m_update:保存所有的record信息,一个vector
db::m_updateUrl2IdMap: 维护从webpage=>recordid的映射
把刚加载的resourceid和本record关联,record里有一个vector来保存所有加载的资源id
5.2 数据保存
当所有AwContent实例被释放时,就会保存数据。所以这个不需要shell触发
Record的保存:
record有两种,一种是browser启动后新加载的资源对应的record,保存在db::m_update里,还有一种是启动时从数据文件里读取的以往的历史记录,保存在m_record里。
优先保存m_update里面的内容。
Loop(for record: records)
{
webpageurl= record.webpageurl; //得到webpageurl
if(webpageurl处理过)continue; //跳过webpageurl相同的
vec<resourceids>= record.getresourceIds(); //拿到该page加载过得resourceid
// 按照record文件格式写文件
write(webpageurl);
write(resourceids)
write(lastvisit time)
}
上面的resourceid保存前,做了一些过滤,去掉resourceurl太长的,和非法的,比如url是空的。然后从新排序得到新的resourceid后写到文件里。
Resource信息的保存:
保存record时,去掉了一些resource,所以这里保存新的resource
5.3 启动时数据的加载和predication数据的从新组织
启动时,record数据加载到db::m_record和m_recordUrl2IdMap里。
resource数据加载到m_resource和m_resourceUrl2IdMap里。
此时,m_update是空的。
initPageLoadPredictionData()对所有的record组织后生成方便predication使用的数据。
基本过程:循环遍历每一个record:
因为一个record对应一个webpageurl.把webpageurlsplit成不同的domain串:news.sina.com.cn可以包括sina.com.cn和news.sina.com.cn.
为每一各不同domain串生成一个predication结构。所以一个record可能生成多个prediction结构。每个结构对应一个domain串。当然不同的record可能会共享相同的domain串。也就会共享同一个predication结构。这样该predication结构的计数就会增加,用以表明该prediction对应的domain的使用频率。这个变量是:PageLoadPredicatio.m_webPageCount。
把该record加载过的资源id都取出来,找到对应资源的信息,加入到每个prediction里面。PageLoadPrediction::m_resourceMap保存了resourceid=>resource 访问频率的映射。
从每个资源url中可以提取资源的domain,所以可以把资源的domain保存到prediction里面。表示该prediction可能需要的资源domain.这个可以用来作为DNS解析和tcp预链接用到的domain.
为了便于保存,可以把所有资源的urldomain保存到一个m_domain里面,然后只在predication里面保存id.
PageLoadPrediction::m_domainMap保存了domainID=> domian访问频率的映射。
当所有的record处理完毕,就有了一如下关系的数据:
不同的网页doman串-----match---- 不同的predication---包含----资源1(id,freq),资源2(id,freq) resource_domain1(id,freq), resource_domain2(id,freq).
上述freq是重复频率的意思。资源和资源的domain是会重复的。他代表了一个域名所对应的若干相关网页里某些资源被重复使用的频率。以此作为筛选排序的准则。
之后,对所有predication中资源和资源domain数据排序,得到:
PageLoadPredication::m_predictedResource--- 排序过后的resourceid
PageLoadPredication::m_predictedDnsDomain和m_predictedTcpDomain---排序过后的resourcedomain.
筛除意义不大的predication:
有些prefection里面的数据量比较少,如果
PageLoadPredicatio::m_webPageCount<PREDICTION_MIN_WEBPAGE_COUNT,m_webPageCount表示为该predication提供过信息的record的数量。也就是网页的数量。或者说为这个domain做过贡献的webpage的数量。数量过低,说明该在domain上访问的网页比较少。可以ignore.该predication.
ResourceDomain 分DNS和tcp两种:就是哪些domain用来prefetchDNS,哪些用来tcppre connect.原则:
在一个predication内:
根据一个domain.freq/predication.m_webPagCOunt就是该domain的使用百分比。
建立tcp链接和dns解析都对使用百分比有要求。tcp链接要求更高。
百分比高的domain优先建立tcp链接,数量达到tcp链接上限后剩下的如果达到dns解析的百分比要求,就进行dns解析。Tcp链接和dns解析都有上限。
//1. if predict percentage is larger thanPREDICTION_MIN_TCP_DOMAIN_RATE, add it to domain candidate for TCPconnect
//2. if predict percentage is larger thanPREDICTION_MIN_DNS_DOMAIN_RATE, add it to domain candidate for DNSresolve
//3. if domain candidate count for TCP connect is larger thanPREDICTION_TOPK_TCPDOMAIN, keep top PREDICTION_TOPK_TCPDOMAIN onesand leave others for DNS resolve
//4. if domain candidate count for DNS connect is larger thanPREDICTION_TOPK_DNSDOMAIN, keep top PREDICTION_TOPK_TCPDOMAIN onesand drop others
//Example:
// d1 (100%), d2 (99%), d3 (98%), d4 (97%), d5 (90%), d6(90%),d7(80%), d8(50%)
// PREDICTION_MIN_TCP_DOMAIN_RATE = 95%,PREDICTION_MIN_DNS_DOMAIN_RATE = 75%
// PREDICTION_TOPK_TCPDOMAIN = 2, PREDICTION_TOPK_DNSDOMAIN =3
//
//based on predict percentage only,
// candidate for TCP connect: d1 (100%), d2 (99%), d3 (98%), d4 (97%)
// candidate for DNS resolve: d5 (90%), d6 (90%), d7 (80%)
//considered restricted prediction count(PREDICTION_TOPK_TCPDOMAIN,PREDICTION_TOPK_DNSDOMAIN)
// candidate for TCP connect: d1 (100%), d2 (99%)
// candidate for DNS resolve: d3 (98%), d4 (97%), d5 (90%)
对每个predication进行上面的domain的筛选后,一个predication内部就有了:
OwnPtr<Vector<uint32_t>> m_predictedDnsDomain; 保存了可以用来dns预取的domainid.
OwnPtr<Vector<uint32_t>> m_predictedTcpDomain; 保存了可以建立链接的domainid.
5.4 predication数据的使用
voidDocumentLoader::startLoadingMainResource()加载网页时,除了可以prefetch该网页自己域名的DNS外。对于资源,可以根据这个网页的url拿到他的domain,根据domain从predication列表里找匹配的predication,然后取出该predication里面保存的m_predictedDnsDomain。这几是需要prefetch的DNSdomain. 需要建立链接的domain在m_predictedTcpDomain里保存着。
6 性能测试
服务器现在不可用,手动脚本测试:
-
运行测试输入网站列表,得到log.
-
分析log得到输出结果,简化输出,每行只需要包括网站地址,耗时。
-
循环执行1,2得到不同的输出log文件在一个目录下,文件名为1.log,2.log.....
-
写一个脚本,加载所有的log文件并把同样网址的耗时取平均值打印出来。
en的top移动站点:
http://m.wikipedia.org
http://m.dictionary.com
http://mozcalc.herokuapp.com/
http://harmmade.com/
http://library.texthtml.net/
http://m.youtube.com
http://acrylicstyle.com/
https://bamm.tv/
http://phone.mensaapp.org/
http://ozon.sonar1.mobi/
http://feinstaubinfo.at/
http://m.wallcup.com/
http://m.thefancy.com/
https://m.airbnb.com
http://beta.drglearning.com/client/
http://cabra.hathix.com/chevre/
http://dictionary.almapps.com/
http://iliterobotics.org/
http://m.le-guide-sante.org/
http://littlealchemy.com/touch/
http://read.alezaa.com
https://www.moneytrail.net/
https://youzeek.com
http://touch.dailymotion.com/
http://www.asahitechnologies.com/
http://www.metacritic.com/
http://www.theoutlinerofgiants.com/
http://www.google.com
https://www.yahoo.com/
http://www.amazon.com
http://m.espn.go.com
http://m.foxnews.com/
http://m.ebay.com/
http://mobile.walmart.com/
http://t.msn.com/
http://www.yandex.ru/
https://m.wellsfargo.com
http://edition.cnn.com/
http://cn.bing.com/
http://m.huffpost.com/us/
http://toma.hk/
http://m.flikie.com
http://www.metacafe.com/
http://geekshadow.github.io/rtl2player/
http://www.menshealth.co.uk/_mobile
http://www.elle.com/_mobile/
http://m.imdb.com
http://www.wikia.com/Wikia
http://m.videobash.com/
没有预测模型代码的build使用buddytest122
有预测模型的build使用buddytest155
使用资源模型后,通过采集记录数据后跑一次如下网站的耗时:
http://m.wikipedia.org 16.77
http://m.dictionary.com 18.40
http://mozcalc.herokuapp.com/ 2.62
http://harmmade.com/ 5.49
http://library.texthtml.net/ 30.02
http://m.youtube.com 30.02
http://acrylicstyle.com/ 30.02
https://bamm.tv/ 30.19
http://phone.mensaapp.org/ 3.01
http://ozon.sonar1.mobi/ 510.35
http://feinstaubinfo.at/ 100.00
http://m.wallcup.com/ 100.00
http://m.thefancy.com/ 100.00
https://m.airbnb.com 100.00
http://beta.drglearning.com/client/ 100.00
http://cabra.hathix.com/chevre/ 100.00
http://dictionary.almapps.com/ 100.00
http://iliterobotics.org/ 100.00
http://m.le-guide-sante.org/ 100.00
http://littlealchemy.com/touch/ 100.00
http://read.alezaa.com 100.00
https://www.moneytrail.net/ 100.00
https://youzeek.com 100.00
http://touch.dailymotion.com/ 100.00
http://www.asahitechnologies.com/ 100.00
http://www.metacritic.com/ 100.00
http://www.theoutlinerofgiants.com/ 4.48
http://www.google.com 14.87
https://www.yahoo.com/ 5.51
http://www.amazon.com 14.14
http://m.espn.go.com 29.94
http://m.foxnews.com/ 19.74
http://m.ebay.com/ 5.10
http://mobile.walmart.com/ 4.51
http://t.msn.com/ 9.51
http://www.yandex.ru/ 29.90
https://m.wellsfargo.com 5.99
http://edition.cnn.com/ 6.94
http://cn.bing.com/ 1.88
http://m.huffpost.com/us/ 9.62
http://toma.hk/ 33.30
http://m.flikie.com 5.94
http://www.metacafe.com/ 30.04
http://geekshadow.github.io/rtl2player/ 6.13
http://www.menshealth.co.uk/_mobile 12.09
http://www.elle.com/_mobile/ 7.12
http://m.imdb.com 26.56
http://www.wikia.com/Wikia 9.84
http://m.videobash.com/ 29.96
http://gorillavid.in/ 4.74
使用没有资源模型的代码跑一次网站列表的耗时:
http://m.wikipedia.org 13.65
http://m.dictionary.com 9.34
http://mozcalc.herokuapp.com/ 2.65
http://harmmade.com/ 4.31
http://library.texthtml.net/ 9.34
http://m.youtube.com 30.11
http://acrylicstyle.com/ 29.93
https://bamm.tv/ 13.02
http://phone.mensaapp.org/ 8.77
http://ozon.sonar1.mobi/ 690.28
http://feinstaubinfo.at/ 100.00
http://m.wallcup.com/ 100.00
http://m.thefancy.com/ 100.00
https://m.airbnb.com 100.00
http://beta.drglearning.com/client/ 100.00
http://cabra.hathix.com/chevre/ 100.00
http://dictionary.almapps.com/ 100.00
http://iliterobotics.org/ 100.00
http://m.le-guide-sante.org/ 100.00
http://littlealchemy.com/touch/ 100.00
http://read.alezaa.com 100.00
https://www.moneytrail.net/ 100.00
https://youzeek.com 100.00
http://touch.dailymotion.com/ 100.00
http://www.asahitechnologies.com/ 100.00
http://www.metacritic.com/ 100.00
http://www.theoutlinerofgiants.com/ 100.00
http://www.google.com 100.00
https://www.yahoo.com/ 100.00
http://www.amazon.com 100.00
http://m.espn.go.com 100.00
http://m.foxnews.com/ 100.00
http://m.ebay.com/ 3.81
http://mobile.walmart.com/ 5.50
http://t.msn.com/ 9.53
http://www.yandex.ru/ 10.42
https://m.wellsfargo.com 6.48
http://edition.cnn.com/ 5.87
http://cn.bing.com/ 1.92
http://m.huffpost.com/us/ 12.85
http://toma.hk/ 30.24
http://m.flikie.com 13.38
http://www.metacafe.com/ 9.27
http://geekshadow.github.io/rtl2player/ 29.81
http://www.menshealth.co.uk/_mobile 29.98
http://www.elle.com/_mobile/ 30.13
http://m.imdb.com 29.82
http://www.wikia.com/Wikia 29.98
http://m.videobash.com/ 30.05
http://gorillavid.in/ 10.85