恕我直言你可能真的不會java第6篇:Stream性能差?不要人云亦云

一、粉絲的反饋

問:stream比for循環慢5倍,用這個是為了啥?
答:互聯網是一個新聞泛濫的時代,三人成虎,以假亂真的事情時候發生。作為一個技術開發者,要自己去動手去做,不要人云亦云。

的確,這位粉絲說的這篇文章我也看過,我就不貼地址了,也沒必要給他帶流量。怎麼說呢?就是一個不懂得測試的、不入流開發工程師做的性能測試,給出了一個危言聳聽的結論。

二、所有性能測試結論都是片面的

性能測試是必要的,但針對性能測試的結果,永遠要持懷疑態度。為什麼這麼說?

  • 性能測試脫離業務場景就是片面的性能測試。你能覆蓋所有的業務場景么?
  • 性能測試脫離硬件環境就是片面的性能測試。你能覆蓋所有的硬件環境么?
  • 性能測試脫離開發人員的知識面就是片面的性能測試。你能覆蓋各種開發人員奇奇怪怪的代碼么?

所以,我從來不相信網上的任何性能測試的文章。凡是我自己的從事的業務場景,我都要在接近生產環境的機器上自己測試一遍。 所有性能測試結論都是片面的,只有你生產環境下的運行結果才是真的。

三、動手測試Stream的性能

3.1.環境

windows10 、16G內存、i7-7700HQ 2.8HZ 、64位操作系統、JDK 1.8.0_171

3.2.測試用例與測試結論

我們在上一節,已經講過:

  • 針對不同的數據結構,Stream流的執行效率是不一樣的
  • 針對不同的數據源,Stream流的執行效率也是不一樣的

所以記住筆者的話:所有性能測試結論都是片面的,你要自己動手做,相信你自己的代碼和你的環境下的測試!我的測試結果僅僅代表我自己的測試用例和測試數據結構!

3.2.1.測試用例一

測試用例:5億個int隨機數,求最小值
測試結論(測試代碼見後文):

  • 使用普通for循環,執行效率是Stream串行流的2倍。也就是說普通for循環性能更好。
  • Stream并行流計算是普通for循環執行效率的4-5倍。
  • Stream并行流計算 > 普通for循環 > Stream串行流計算

3.2.測試用例二

測試用例:長度為10的1000000隨機字符串,求最小值
測試結論(測試代碼見後文):

  • 普通for循環執行效率與Stream串行流不相上下
  • Stream并行流的執行效率遠高於普通for循環
  • Stream并行流計算 > 普通for循環 = Stream串行流計算

3.3.測試用例三

測試用例:10個用戶,每人200個訂單。按用戶統計訂單的總價。
測試結論(測試代碼見後文):

  • Stream并行流的執行效率遠高於普通for循環
  • Stream串行流的執行效率大於等於普通for循環
  • Stream并行流計算 > Stream串行流計算 >= 普通for循環

四、最終測試結論

  • 對於簡單的数字(list-Int)遍歷,普通for循環效率的確比Stream串行流執行效率高(1.5-2.5倍)。但是Stream流可以利用并行執行的方式發揮CPU的多核優勢,因此并行流計算執行效率高於for循環。
  • 對於list-Object類型的數據遍歷,普通for循環和Stream串行流比也沒有任何優勢可言,更不用提Stream并行流計算。

雖然在不同的場景、不同的數據結構、不同的硬件環境下。Stream流與for循環性能測試結果差異較大,甚至發生逆轉。但是總體上而言

  • Stream并行流計算 >> 普通for循環 ~= Stream串行流計算 (之所以用兩個大於號,你細品)
  • 數據容量越大,Stream流的執行效率越高。
  • Stream并行流計算通常能夠比較好的利用CPU的多核優勢。CPU核心越多,Stream并行流計算效率越高。

stream比for循環慢5倍?也許吧,單核CPU、串行Stream的int類型數據遍歷?我沒試過這種場景,但是我知道這不是應用系統的核心場景。看了十幾篇測試博文,和我的測試結果。我的結論是: 在大多數的核心業務場景下及常用數據結構下,Stream的執行效率比for循環更高。 畢竟我們的業務中通常是實實在在的實體對象,沒事誰總對List<Int>類型進行遍歷?誰的生產服務器是單核?。

五、測試代碼

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>junitperf</artifactId>
    <version>2.0.0</version>
</dependency>

測試用例一:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.Arrays;
import java.util.Random;

public class StreamIntTest {

    public static int[] arr;

    @BeforeAll
    public static void init() {
        arr = new int[500000000];  //5億個隨機Int
        randomInt(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntFor() {
        minIntFor(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntParallelStream() {
        minIntParallelStream(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntStream() {
        minIntStream(arr);
    }

    private int minIntStream(int[] arr) {
        return Arrays.stream(arr).min().getAsInt();
    }

    private int minIntParallelStream(int[] arr) {
        return Arrays.stream(arr).parallel().min().getAsInt();
    }

    private int minIntFor(int[] arr) {
        int min = Integer.MAX_VALUE;
        for (int anArr : arr) {
            if (anArr < min) {
                min = anArr;
            }
        }
        return min;
    }

    private static void randomInt(int[] arr) {
        Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = r.nextInt();
        }
    }
}

測試用例二:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.ArrayList;
import java.util.Random;

public class StreamStringTest {

    public static ArrayList<String> list;

    @BeforeAll
    public static void init() {
        list = randomStringList(1000000);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringForLoop(){
        String minStr = null;
        boolean first = true;
        for(String str : list){
            if(first){
                first = false;
                minStr = str;
            }
            if(minStr.compareTo(str)>0){
                minStr = str;
            }
        }
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void textMinStringStream(){
        list.stream().min(String::compareTo).get();
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringParallelStream(){
        list.stream().parallel().min(String::compareTo).get();
    }

    private static ArrayList<String> randomStringList(int listLength){
        ArrayList<String> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int strLength = 10;
        StringBuilder buf = new StringBuilder(strLength);
        for(int i=0; i<listLength; i++){
            buf.delete(0, buf.length());
            for(int j=0; j<strLength; j++){
                buf.append((char)('a'+ rand.nextInt(26)));
            }
            list.add(buf.toString());
        }
        return list;
    }
}

測試用例三:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.*;
import java.util.stream.Collectors;

public class StreamObjectTest {

    public static List<Order> orders;

    @BeforeAll
    public static void init() {
        orders = Order.genOrders(10);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderForLoop(){
        Map<String, Double> map = new HashMap<>();
        for(Order od : orders){
            String userName = od.getUserName();
            Double v; 
            if((v=map.get(userName)) != null){
                map.put(userName, v+od.getPrice());
            }else{
                map.put(userName, od.getPrice());
            }
        }

    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderStream(){
        orders.stream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderParallelStream(){
        orders.parallelStream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }
}


class Order{
    private String userName;
    private double price;
    private long timestamp;
    public Order(String userName, double price, long timestamp) {
        this.userName = userName;
        this.price = price;
        this.timestamp = timestamp;
    }
    public String getUserName() {
        return userName;
    }
    public double getPrice() {
        return price;
    }
    public long getTimestamp() {
        return timestamp;
    }

    public static List<Order> genOrders(int listLength){
        ArrayList<Order> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int users = listLength/200;// 200 orders per user
        users = users==0 ? listLength : users;
        ArrayList<String> userNames = new ArrayList<>(users);
        for(int i=0; i<users; i++){
            userNames.add(UUID.randomUUID().toString());
        }
        for(int i=0; i<listLength; i++){
            double price = rand.nextInt(1000);
            String userName = userNames.get(rand.nextInt(users));
            list.add(new Order(userName, price, System.nanoTime()));
        }
        return list;
    }
    @Override
    public String toString(){
        return userName + "::" + price;
    }
}

歡迎關注我的博客,裏面有很多精品合集

  • 本文轉載註明出處(必須帶連接,不能只轉文字):字母哥博客。

覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力! 。另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注。

  • 《手摸手教你學Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2一本通》
  • 《實戰前後端分離RBAC權限管理系統》
  • 《實戰SpringCloud微服務從青銅到王者》
  • 《VUE深入淺出系列》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

蒲公英 · JELLY技術周刊 Vol.12 尤雨溪新作 Vite, 你會支持么?

「蒲公英」期刊,每周更新,我們專註於挖掘「基礎技術工程化跨端框架技術圖形編程服務端開發桌面開發人工智能」等多個大方向的業界熱點,並加以專業的解讀;不僅如此,我們還精選凹凸技術文章,向大家呈現團隊內的研究技術方向。

抬頭仰望,蒲公英的種子會生根發芽,如夏花絢爛;格物致知,我們登高遠眺、滄海拾遺,以求積硅步而至千里。

登高遠眺

天高地迥,覺宇宙之無窮

前端框架

Vue3 Composition API 提案

Vue3 其中一個重量級的特性就是 Composition API,它能幫助我們更好地組織代碼。本網頁是 Composition API 的草案,詳細介紹了 Composition API 的設計動機、設計細節、具體 API 用法等。文章篇幅較長,推薦找一個悠閑的周末,泡上咖啡,帶上耳機,細細品讀一番。

工具測評: React Hook Form VS Formik

使用 React 構建表單是一件痛苦的事情,官方推薦了 Formik。本文對使用 Formik 和 React Hook Form 構建表單進行了比較,得出 React Hook Form 比 Formik 更易用、更高效的結論。如果你正巧在這方面有困惑,可實踐嘗試體驗。

Quark-h5 — 從零開始的可視化編輯器

想必你一定使用微場景生成工具製作過炫酷的 h5 頁面,除了感嘆其神奇之處有沒有想過其實現方式呢?本文從零開始實現一個 H5 編輯器項目完整設計思路和主要實現步驟,並開源前後端代碼。有需要的小夥伴可以按照該教程從零實現自己的H5編輯器。

圖形編程

初探虛幻引擎5

5月底, 遊戲公司Epic揭開了虛幻5引擎神秘的面紗,此次更新包含 Nanite虛擬微多邊形 和 全新的動態全局光照Lumen 兩大核心技術,然後展示了該引擎運行在PS5上實時渲染效果,其逼真的光照和媲美電影的細節震驚了整個行業。

工程化

Vite — 入門到實戰

Vite 是 Vue 技術生態新推出的開發工具,針對 Vue 應用的無打包開發服務器,開發者無需藉助 webpack 等打包工具,即可直接在瀏覽器中預覽 Vue 項目。Vite 的原理與本技術周刊之前介紹過的 snowpack 有着異曲同工之處,Vite 本身也表示一部分靈感來自 snowpack 項目。本文從 0 開始一步一步實現了一個簡易版本的 Vite 來講解 Vite 的技術原理,讀過本文之後,再去閱讀 Vite 的項目源碼,相信會有不小的收穫。

人工智能

杜克大學出品 AI 黑科技 PULSE 算法,讓你的照片有碼變高清

近日杜克大學開源了新型超分辨率圖像算法 PULSE,可將16×16像素的低分辨率人像放大到1024×1024像素的高分辨率。

工具推介

手把手教你快速搭建專屬的 StoryBook

Storybook是一個輔助UI控件開發的工具。通過story創建獨立的控件,讓每個控件開發都有一個獨立的開發調試環境。 Storybook的運行不依賴於項目,開發人員不用擔心由於開發環境、依賴問題導致不能開發控件。Storybook支持的框架覆蓋主流的框架(React、Vue、Angular)。 由於使用React作為技術棧,本文將介紹使用react的項目如何配置Storybook環境。

滄海拾遺

滄海拾遺,積跬步以至千里

ELF – 靈活可擴展的 HTML5 構建工具

前端工程化的問題由來已久,除了尤老師正在努力的方向,還出現過很多優秀的小工具幫助我們解決各個方面的問題,ELF 就是其中一種解放我們重複勞動的構建工具之一。

用 Git 鈎子進行簡單自動部署

除了這些工具,工程化中也還是有很多小問題,可以用很多方法去解決,自動化部署就是其中之一,如果你還不懂的如何利用 Git Hook 完成自動化部署的方法,趕緊補起這一課吧,未來正在向你招手~

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

【代碼修鍊系列分享】改掉這些壞習慣,還怕寫不出健壯的代碼?(一)

Code Review 是一場苦澀但有意思的修行。

近期對團隊負責的項目,進行了一次 Code Review,代碼評審過程中遇到的那些編碼壞習慣,笑的合不攏嘴。不過,評審中很多代碼編寫問題,以往都多次提及過,所以還是按奈不住心中怒氣的小火苗。

作為用代碼編寫人生的程序員,能擁有寫一手健壯代碼的本領,那絕對很有必要。因為健壯的代碼能夠把 Bug 扼殺在搖籃里,能夠讓問題止步於上線前。

那麼,怎樣才能練就寫出健壯代碼的本領呢?

本次着重談談那些代碼編寫時的一些壞習慣,改掉這些壞習慣,相信會向健壯代碼邁進一大步。

一、編碼時易忽略性能的壞習慣 

壞習慣一:調用低效的構造器,創建包裝類型的對象

反例:

正解:

解惑:使用 Long.valueOf(long) 代替 new Long(long),可以提高性能。

如 Long 源碼所示,如果當傳入的值介於 -128~127 時,會優先從緩存中返回緩存的值,而不是進行 new,充分利用空間換取時間,所以當值介於 -128~127 時,採取 Long.valueOf(long) 的效率要比 new Long(long) 快很多。

建議:

  • 凡是涉及到 Long, Integer, Short, Character 以及 Byte 創建對象時,優先採用高效的 valueOf() 方法,而不是直接用低效構造器創建實例。
  • 享元設計模式在這兒用到了,什麼是享元模式?(留個作業)

壞習慣二:使用 keySet 迭代器迭代 Map,獲取對應的 value。

反例:

正解:

解惑:keySet 方式遍歷 Map 的性能不如 entrySet 性能好。

如果採用 keySet 的方式獲取 Map 中 key,然後通過 key 獲取 Map 對應的 value,如上圖 HashMap 源碼所示,每次都需要通過 key 去計算對應的 hash 值,然後再通過 hash 值獲取對應的 value,效率會低不少。

建議:

  • 如果想獲取 Map 對應的 key 和 value,則推薦使用 entrySet。
  • 如果只是單純獲取 Map 對應的 key,則推薦使用 keySet。

壞習慣三:使用 new Date().getTime() 獲取當前時間戳。

反例:

正解:

解惑:如下圖 Date 源碼所示,Date 構造方法中最終還是調用了 System.currentTimeMillis() 方法來獲取時間戳。

建議:

  • 獲取當前毫秒數採用 System.currentTimeMillis(),而不是new Date().getTime(); 
  • 獲取更加精確的納秒級時間值,採用 System.nanoTime;
  • 在 JDK8 中,針對統計時間等場景,建議使用 Instant 類。

壞習慣四:循環中使用 ”+“ 號拼接字符串。

反例:

正解:推薦使用 StringBuilder/StringBuffer 進行字符串拼接。

解惑:「Java 程序該怎麼優化?技巧篇」以前的這篇分享做過試驗,本次不贅述。

二、編碼時易犯的一些小毛病 

毛病一:變量作為 equals() 方法的調用方。

反例:

正解:

解惑:totalCount 應該作為方法  equals() 的調用方,而不是參數 作為調用方,因為參數作為調用方會出現空指針異常。

建議:

  • 字符串的比較,常量建議當做 equals() 方法的調用方;
  • 字符串判斷空,建議用項目中的工具類。

毛病二:對象為 null 的檢查滯后。

反例:

正解:請在使用 data 對象前,做好是否為 null 的判斷。

解惑:後置對象為空的檢查,可能會導致空指針異常的發生。

毛病三:要求傳入非空的方法,傳入空值。

反例:

正解:signInfo 變量的值可能存在為空的情形,導致發生空指針異常。

建議:發生異常的時候,方法該終止就終止;盡量做好防禦性編程,該校驗的參數進行必要的校驗。

三、寄語寫最後 

常在河邊站哪有不濕鞋,再牛逼的碼農,編碼也會有失誤的時候,很有必要藉助一款代碼檢查工具,做最後一道防線。

在這裏,推薦 FindBugs、Checkstyle、SonarQube 三款代碼檢查工具,不過我用的最多的當屬 FindBugs,可以拿去一試,使用門檻幾乎為零。

好了,編碼中易犯的那些臭毛病,本次就談到這裏,不知道有多少條是觸動了你的心弦,希望有則改之。

關注同名公眾號:一猿小講,回復「1024」可以獲取精心為您準備的職場打怪進階資料。

一起聊技術、談業務、噴架構,少走彎路,不踩大坑,會持續輸出原創精彩分享,敬請期待!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

【故障公告】阿里雲 RDS 實例 CPU 100% 故障引發全站無法正常訪問

非常抱歉,今天凌晨 3:20~8:30 左右,我們使用的阿里雲 RDS 實例 SQL Server 2016 標準版突然出現 CPU 100% 故障,造成全站無法正常訪問,由此給您帶來巨大的麻煩,請您諒解。

問題很奇怪,故障期間是數據庫服務器負載極低的時間段。從阿里雲 RDS 控制台 CloudDBA 看,故障期間下面的一個 SQL 語句大量執行,並且極其消耗 CPU 。

開始我們以為是這個 SQL 語句引發的故障,但排查下來這個 SQL 語句本身並沒有性能問題,而且已經使用了至少6個月。

最終恢復正常是通過 RDS 的2次主備切換,當發現故障后,我們立即進行主備切換,但切換后 CPU 依然 100% ,然後我們排查 SQL 語句的問題,排查未果,然後又進行一次主備切換,才恢復正常。

事後分析后發現應該是第一次主備切換沒有成功完成,阿里雲 RDS 控制台查看不到主備切換日誌,但2次切換,只有第2次收到郵件通知,由此可以推斷。

您的雲數據庫RDS實例:xxx(名稱:enable or disable task fetching while rds2slb transgfer.)任務觸發切換完畢,請檢查程序連接是否正常,建議設置自動重連機制以避免切換影響。

問題的原因有待進一個分析,再次抱歉由此給您帶來的麻煩。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

《T-GCN: A Temporal Graph Convolutional Network for Traffic Prediction》 論文解讀

論文鏈接:https://arxiv.org/abs/1811.05320

最近發現博客好像會被CSDN和一些奇怪的野雞網站爬下來?看見有人跟爬蟲機器人單方面討論問題我也蠻無奈的。總之原作者Missouter,博客鏈接https://www.cnblogs.com/missouter/,歡迎交流。

整理、精鍊了一下這篇論文的思路。

Abstract:

交通預測的難點在於交通拓撲網絡複雜的結構與隨時間動態發生的交通變化;為了提取交通網的空間與時間特徵,文章提出了一種時間性的圖卷積網絡模型,結合了門級循環單元(GRU)與圖卷積網絡(GCN)。其中圖卷積網絡被用於提取複雜拓撲結構中的空間性信息,而門級循環單元則為了提取時間關係,被用於學習交通數據中的動態數據。

Introduction:

交通預測過程:分析交通道路狀況,包含流量、速度、密度;挖掘交通模式,同時對道路上的交通狀況進行預測。

預測結果:交通管理者預測擁堵狀況、限制交通工具的科學基礎、出行者效率選擇出行工具、路線的保障。

難點:

1、 空間關係:交通流的變化被拓撲結構的城市交通網主導,上游的交通狀態通過傳輸作用影響下游的交通狀態,而下游交通狀態通過反饋作用再影響上游交通。

2、 時間關係:交通流隨時間動態變化,主要表現在周期性和趨勢上;現有的交通狀況被前一刻的交通狀況影響。

現存交通預測方法缺陷:一些交通預測方法(ARIMA、Kalman filtering model,etc)只關注了交通狀況的動態變化而忽視了空間關係,導致交通狀態的變化不被道路網約束,同時一些模型嘗試使用卷積神經網絡進行空間性建模,但這些模型一般只使用於歐幾里得類型的數據(規則矩陣、圖像等),無法在拓撲結構的城市交通網絡中運作。

T-GCN貢獻:

1、 結合了GCN與GRU,內容與abstract重複,不做多提。

2、 T-GCN的預測結果展示了一個不同視角下的穩定狀態,表示T-GCN除了預測短周期的變化,還能預測長周期的變化。

3、 我們使用兩個現實的數據集評估模型,相較其他預測模型,減少了1.5%~57.8%的預測錯誤率。

Related work:

交通預測方法分類:模型驅動的方法、數據驅動的方法;

模型驅動的方法:解釋即時、穩定的交通關係如交通流量、速度與密度,需要複雜細緻的系統建模與巨大的算力,且由於諸多因素的影響,現實環境中交通數據的多樣性無法被精確描繪。

數據驅動的方法:基於數據統計所得規律推斷交通狀況的變化。不分析物理屬性與交通系統的動態變化有着較高的複雜度。其中的historical average model不需要假設、計算過程簡單,但精確度不佳。

擁有更高精確度的模型被提出,分為兩種:含參數的與非參數的。

含參模型假定回歸函數,參數在對原始數據的處理過程中確定。傳統含參的模型是在系統模型為靜態的假設基礎上建立的,反映不了交通系統的非線性與不確定性,也克服不了交通事故等突發隨機事件的困難。不含參數的模型只需要足夠的歷史數據就能自動學習靜態規律特徵,但先前形如LSTM、GRU的模型僅僅考慮了時間關係而忽略了空間關係,不能精確預測道路上的交通信息,如何充分利用空間信息成了交通預測的關鍵,而利用CNN進行空間關係提取的模型雖然在交通預測上取得了顯著進步,但無法應用於複雜拓撲結構的城市交通網,隨着GCN研究的深入,拓撲結構空間特徵的提取問題也得到解決。

(這段論文寫的有點長…堆了一堆文字讀的我有點難受)

Methodology:

問題定義:根據歷史數據預測某一特定時間段的交通信息,交通數據通常被定義為速度、密集程度、交通流。使實驗不失普遍性,在實驗章節使用交通速度作為交通數據的代表。(Without loss of generality, we use traffic speed as an example of traffic information in experiment section.這句語感有點僵了)

定義:設定無權圖G=(V, E),V、E分別代表道路點與邊,N為道路的數量,設定鄰接N*N矩陣A表達道路與道路之間的聯繫;設定N*P特徵矩陣X,P代表點屬性特徵的數量,即歷史時間序列的長度,使用N*i矩陣 表示在時刻i,每條道路上的速度,屬性特徵也可以是交通速度、交通流等屬性。

問題轉化:交通空間-時間性預測被轉化為學習以拓撲圖G為前置與特徵矩陣X的映射函數,計算得到在下T時刻的交通信息:

過程概覽:首先以長度為n的歷史時間序列數據作為輸入,使用GCN接收拓撲結構的空間信息,其次將接收到的空間、時間信息輸入GRU當中,獲取各個單元間的動態信息變化,以提取時間性的特徵,最後在全連接層獲得結果。

空間關係建模:利用GCN提取圖結構的數據,通過給定的鄰接矩陣A、特徵矩陣X,GCN在圖上通過提取相鄰結點特徵構建傅里恭弘=叶 恭弘域,使用堆疊的卷積網絡,表示為:

Â為A與單位矩陣I相加所得, D為度矩陣, Hl為第l層的輸出, θl包含該層的參數, σ代表非線性回歸的激活函數。(我懷疑論文這裏的上標打錯了)

兩層GCN模型可被表示為:

其中 ,P*H的 矩陣代表從輸入到隱藏層的權重,P為特徵矩陣的長度,H為隱藏單元的數量;H*T的W1代表從隱藏層到輸出層的權重, f(X,A)∈RN*T代表長度為T的預測輸出,ReLU()作為修正線性單元,作激活層用。

時間關係建模:被廣泛應用的循環神經網絡因梯度消失/爆炸的原因,不適用於長周期的預測;LSTM與GRU作為循環神經網絡的變種,克服了上述問題。其共同原理都是利用門級機制儲存盡可能長的周期信息;LSTM因其複雜的結構,GRU結構更加簡單,故計算時間更短。

 

圖中ht-1表示t-1時刻的隱藏狀態, xt表示t時刻的交通信息, rt代表重置門,用於控制先前時刻狀態信息的度量; ut為上傳門,用於控制上傳到下一狀態的信息度量; ct為t時刻時儲存的信息, ht為t時刻的輸出狀態;總的來說,GRU通過獲取t-1時刻的隱藏狀態與當時的交通狀態信息得到t時刻的交通信息。

T-GCN

 

T-GCN的結構如圖,右側為T-GCN的處理單元: 為t-1時刻的輸出,GC為圖卷積過程,ut 、 rt分別為上傳門與重置門, 為t時刻的輸出。具體計算過程為:

f(A,X)代表前文定義的GCN計算過程,w與b代表訓練過程中的權重與偏移量。(很大部分照搬了GNU的公式)

損失函數:

目標:最小化真實交通速度與預測交通速度的誤差。Y分別代表真實速度與預測速度,損失函數如下:

其中 λLreg用於防止過擬合。

Experiments:

選取數據集

1、SZ-taxi:數據分為兩部分,156*156的鄰接矩陣表示路與路之間的空間關係,描述每條路上隨時間變化的特徵矩陣。

2、Losloop:由鄰接矩陣與特徵矩陣組成,鄰接矩陣由交通網絡中的傳感器計算;同時作者對數據集中的殘缺部分使用線性填充的方法進行了補全。

輸入的數據全部進行了歸一化處理,80%的數據用於訓練,而20%的數據被用於測試。實驗對接下來15、30、45、60分鐘的交通速度進行預測。

評價指標:

文章列出了五個用於評價T-GCN預測表現的指標:

1、均方根誤差:

2、平均絕對誤差:

3、準確率:

4、 確定係數:

5、可釋方差值:

其中yj’i分別代表第j次時間、第i條路的真實交通信息與預測信息,M為時間樣本的數量,N為路的數量,Y分別代表 與 的集合, 帶上劃線的Y為Y的平均數。

R2與var用於計算相關係數,衡量預測結果表示實際數據的能力,數值越大則預測能力越優越。

選取模型參數:

超參數:包括學習率(0.001)、抓取數量(32)、訓練輪數(5000)、隱藏單元數量(通過多次實驗取最優預測效果)

針對SZ-taxi文章選取[8,16,32,64,100,128],分析預測準確率的變化,得到不同隱藏單元數量下RASE與MAE、Accuracy、R2 、var的值如圖:

 

顯然數量為100時效果是最好的(真有這麼巧的事情嘛…)。隱藏單元超過一定數量性能下降的原因在於數據單元超過一定值後計算複雜度增加,且訓練數據會出現過擬合現象。

實驗結果:

將T-GCN預測效果與baseline對比,包括HA、ARIMA、SVR、GCN、GRU,所得實驗數據如下:

 

高預測精準度:門級循環網絡的預測精度顯著高於其他baseline算法,HA、ARIMA、SVR等算法因難以處理複雜、非固定的時間數據而預測效果不佳,GCN預測效果不佳的原因僅僅在於僅考慮了空間特徵而忽略了交通數據是一個典型的時間序列數據這一事實。

ARIMA比HA檢測效果弱的原因在於ARIMA不適用於長周期的檢測,且計算每個節點誤差的方式導致一些數據中的波動會導致最終的計算錯誤。

時空預測能力:為了檢驗模型是否能夠從交通數據中提取時空特徵,文章將模型與GCN、GNU模型進行了比較:

長周期預測能力:在不同的時間範圍(文章只寫了“horizon”,根據前後文認定為是時間範圍)下,T-GCN都能獲得最好的預測效果且預測結果擁有較小的變化趨勢,證明了T-GCN擁有更好的長周期預測能力;文章以T-GCN在不同時間點的結果作為論證:

 

干擾分析與魯棒性:通過向數據中添加兩種不同的噪聲檢驗T-GCN的魯棒性,高斯分佈、泊松分佈的 值作為自變量被改變,分別添加到兩種數據集上,得到結果:

 

由評估指標的隨噪聲的變化細微可以得出T-GCN具強魯棒性的結論。

模型解釋:

文章將T-GCN所得的預測結果與真實數據可視化得到結論:

 

模型對於峰谷值的預測效果不佳,原因在於T-GCN在傅里恭弘=叶 恭弘域中定義了平滑過濾器,通過不斷移動過濾器提取空間特徵,造成了全局預測中的細微變化,使得曲線中的峰變得平滑。

文章還指出預測與實際之間存在固定誤差,由數據集的特殊性造成,即SZ-taxi數據集表示某時刻出租車的數量可能為0,但實際道路上車輛的數量不一定為0;

對這篇論文的解讀就到這裏結束了,Methodology部分使用GCN與GNU的理論,設計了新的計算單元;Experiment部分很讓我受教,對實驗的條件、前提設定的非常詳盡,在介紹數據集與衡量指標后,對選取實驗參數作了嚴格的實驗論證;對實驗結果進行了詳盡的分析,通過與其他幾個模型的預測結果進行對比,從空間、時間、時間跨度預測、精度、魯棒性等角度對模型的優越性進行對比論證,數據的選取與展示都精確地契合了論證的論點;最後關於傅里恭弘=叶 恭弘與峰谷偏差的模型解釋很精妙。

關於GNU、LSTM的學習解讀會繼續跟進。同時也會對這篇論文git上的開源代碼進行解讀與實驗復現。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

重構下載功能模塊

背景

最近項目新上線的版本中,出現了很多涉及下載相關的bug;經過艱難的代碼調試排查,可還是沒有準確定位問題。主要還是由於代碼年久,經過多代人的修修補補,到處都是“壞味道”的代碼。跟項目經理溝通過後,同意重構這部分的代碼。下面就簡單記錄一下重構的過程。

項目現狀

項目最早可追溯至2014~2015年,分為配置管理網站(Server),WPF桌面軟件(Client)。桌面軟件又分為:用戶操作界面WPF,後台服務 Windows Services 程序。
在後端Services中,維護一個本地的小型Firebird數據庫,為WPF提供數據和用戶狀態保持;實現了從遠程服務器同步數據到本地,訪問遠程接口下載管理數據上報等功能。而我這次要做的就是,重寫下載管理。下面是關於舊代碼的分析:

  1. 三種不同的下載觸發機制:軟件啟動觸發用戶觸發數據同步到本地觸發
  2. 八種不同的資源下載,分別在不同的表中管理;
  3. 有五套複製粘貼的代碼:軟件啟動(兩套)、用戶觸發(兩套)、數據同步(一套);
  4. 大量無意義嘗試,比如下載失敗404后,短時間內沒有必要再次嘗試;
  5. 狀態管理不統一,有的下載完成狀態改為 ReadyForUse,有的下載完成狀態改為 DownloadSuccess;
  6. 執行方法語義重疊,比如 DownloadPause 和 DownloadStop,DownloadFinished 和 DownloadSuccess;
  7. 大段註釋代碼,這些代碼基本永遠不會再使用的;

重構設計

鑒於項目是在運行階段,一方面要考慮時間成本,另一方面要保證原有的運行方式。在當前軟件的框架下,把重寫的代碼限定在下載模塊內部,對外的接口不做改變。
重構代碼的抽象設計,也是依據當前項目的實際情況,盡可能的通用和易擴展。設計的UML類圖如下:

下面的三個虛類,是下載管理流程的核心,所有的控制邏輯都包含在其中。

  1. DownloadResourceAbstract 虛類 資源文件的基本信息: DatabaseIDResourceID為了兼容不同數據庫表的設計;DownloadPriority 預設優先級;DownloadImmediatelyPriority 用戶觸發的下載,優先級最高,立即執行;其它字段涵義可見字段名稱;
  2. DownloadWorkItemAbstract 虛類 資源下載操作類,包含下載狀態,開始,停止,MD5檢查,狀態更改的回調方法;與一對一關係;
  3. DownloadControlerAbstract 虛類 控制同時下載的數量,根據優先級控制下載的順序;與一對多關係

下面的類,是具體下載方法的實現;當前的項目只有HTTP下載,類圖中也只實現了HTTP的下載的管理功能:

  • 可擴展下載方法設計:FtpClientHttpClient繼承自DownloadClientAbstract
  • 關聯 HTTP 下載,分別實現的子類 HttpDownloadWorkItemHttpDownloadControler

對不同類型的資源文件,組合不同的下載方式:

  • AutoHttpDownloadDealer: 自動下載的資源 + HTTP 下載;
  • UserOperHttpDownloadDealer: 用戶手動操作的資源 + HTTP 下載;
  • 由數據同步到本地的觸發下載資源 + HTTP下載;(未在UML圖中畫出)

發現bug的真實原因

在重構測試階段,意外發現了bug的原因:前段時間更換了文件服務器,而新的文件服務器,不支持斷點續傳,HTTP Status 返回的不是預期的Partial Content(206),而是OK(200);下載文件不存在時,HTTP Status 返回的也不是 404,而返回自定義的 Response Text 。。。

由於這兩個不合理的地方,導致軟件的下載功能,出了好多莫名其妙的bug,再疊加到處複製 + 粘貼“壞味道”的代碼,修復起來異常繁瑣,於是就有了重構這件事情。

個人感受

在當前的項目中,也做了幾次小範圍的重構,要麼代碼影響範圍小,要麼只是在原代碼基礎上套個框架。而這次是徹底幹掉原代碼,重新設計,重新寫。下面是代碼重寫完成后 git merge 的 log:

Showing 1107 revision(s), from revision 6092a531 to revision f336c077 – 1 revision(s) selected, 166 file(s) selected; line: 5226(+) 5251(-) files: modified = 118 added = 28 deleted = 20 replaced = 0

從上面的log匯總信息,好像也不足以展示修改工作量。這些修改大約花費了我一周的時間,自願工作日加班,自願周末加班(有時候代碼寫上頭了,還得強迫自己下班休息去)。開始的前三天的時間,邊讀舊代碼,邊搭建新的框架,嘗試把舊代碼融入到新設計中;後期轉換思路,理解代碼邏輯,把代碼邏輯融入到新設計中

這樣就可以放開手腳的干起來了,寫代碼的時候,感覺時間過的好快,如有神助般各種代碼中的細節,也會不假思索的寫出來。在下階段單元測試中,非常順滑,基本沒有發現什麼bug,真的是酣暢淋漓。

最最讓我寒心的是,好不容易重構測試完代碼了,壓抑着無比激動的心情,希望組裡的同事給個积極肯定的反饋,可連一個“卧槽”也沒有,就行往常平淡的工作日。。。項目經理也是很淡然的說,要儘快拿給測試。

尾聲

即使我的努力和心血沒有得到,預期中的肯定,我也要把這個過程留存記錄下來:把重構的代碼剔除項目信息,再提取合併通用的部分,重新梳理成一個類庫Github 地址;寫一篇文章來記錄一下。

其實,後面我細細回想了一下。如果最開始項目設計的下載功能是,把所有下載資源放在同一張數據庫表中管理,是不是就那不就更簡單了?如果最最早期的設計做好了,後期可以省掉多少功能擴展、修復bug、重構。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

鄭愷現身上海機場飛往北京,表情冷酷

2019年10月22日,鄭愷現身上海機場飛往北京,當天她身穿淺藍色衛衣搭配寬鬆牛仔褲,腳白色運動鞋,墨鏡遮面打扮時尚帥氣,表情冷酷,自推行李行走間星范十足。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※男人為什麼總愛舒壓按摩台北外送茶交流呢?

※解決小三、二奶問題,斬桃花推薦

※網友現身說法:神祕的降頭術?

※挽回老公/男友的心,和合術諮詢

※老司機曝包養內幕

宋妍霏現身上海機場飛往深圳,行走間笑容燦爛

2019年10月21日,宋妍霏現身上海機場飛往深圳,當天她身穿米色字母毛衫搭配黑色牛仔短褲,腳白色運動鞋秀白皙美腿,肩背棕色帆布包,長發披肩紅唇膚白打扮時尚清新,行走間笑容燦爛星范十足。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

電子煙有爭議?真相解密

桃園外送茶莊、喝茶吃宵夜首選

※愛情診療室,感情挽回推薦

※挽回老公/男友的心,和合術諮詢

※揭開降頭祕辛?

※老司機曝包養內幕

小七終於長大了,逛街獨寵大姐小貝卻被冷落

當地時間10月22日,大衛·貝克漢姆(David Beckham)帶女兒哈珀·塞文·貝克漢姆(Harper Seven Beckham)現身美國比弗利山莊購物。從小就被小貝當成公主的小七,別看才8歲,逛街時卻特別像大人呢,而且這樣的逛街活動似乎也是這位白富美日常生活的一部分啦!

當天小七牽手一個美女大姐姐,身穿印花弔帶上衣搭配牛仔短褲,作為一個比較肉肉的星二代,小七被很多網友稱之為“中國版的王詩齡”,看身材的確是有幾分相似呢。現在小七也是越來越精緻了,雖然身材圓滾滾卻非常可愛呢,而且顏值也是比較像小貝的一個,妥妥的美人坯子。

雖然身材肉嘟嘟,但是小七卻是一個非常活力的女孩,和身邊大姐姐在一起小動作非常多,看到自己喜歡的東西更是興奮得直接跑過去了。雙夾趾鞋拖,依舊健步如飛,也是顯出了自己很接地氣的一面了。

從小在爸爸懷裡沒下來過的小七,現在真是長大了,逛街時也不粘着爸爸了,而是一直牽着旁邊美女姐姐的手,看起來關係非常親密呢。而且美女小姐姐的顏值也很高呀,身穿一條碎花短裙,着帆布鞋,很清新文藝范。

再來看這邊備受冷落的老爸,雖然是一起逛街卻都沒用拍到同框畫面,看這表情,是不是超無奈?身穿黑白拼接t恤,反戴棒球帽的小貝,身材肌肉明顯,雖然最近也難免顯出了老氣,可是並不影響他的帥氣呢。女兒長大了開始有了自己的朋友圈,不再這麼粘着老父親了,表面上看輕鬆了很多實際上老父親的心裏肯定是不怎麼舒坦吧,有點心疼小貝了!

這邊老爸陪小七逛街被冷落顯得很無奈啊,那邊大布Brooklyn Beckham卻被拍到和新女友一起約會,忙得不亦樂乎呢。女友被稱為低配版Kylie Jenner,夜晚和一幫朋友外出,沒什麼過於親密的舉動。而前女友Hana和新男友也當街熱吻曝光新戀情了,看來兩個人都從上一段感情走出來了。

新女友是25歲女演員Phoebe Torrance,據說倆人在餐廳親密摟抱整晚,但是這次被拍到一起現身也算是終於有了實錘。新女友比大布大5歲,外媒還說她是山寨版維多利亞,但七七覺得和貝嫂還是挺有差距的。目前新女友Phoebe在ins上和布魯克林互關,還關注了弟弟羅密歐。

小七終於長大了,逛街獨寵大姐小貝卻被冷落,大布和新女友約會忙!大布別看才20歲,可是光是公開的戀情都不知道已經有過多少段了,現在除了和爸媽一起出席活動外,就是忙着與女友約會。而這邊被爸爸寵上天的小七也終於長大了,不再和老爸膩歪,被冷落的小貝是不是要傷心了?

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※全台最大電子煙交易平台?

新竹外送茶交流平台

※被爛人糾纏,如何斬桃花?

※揭開降頭祕辛?

※挽回老公/男友的心,和合術諮詢

※老司機曝包養內幕

Ella與帥氣老公走機場,穿同款紙袋褲甜蜜秀恩愛

儘管已經結婚多年,SHE組合成員Ella陳嘉樺的婚後生活一直都非常低調,和高富帥老公賴斯翔也鮮少出現在公眾面前。近日,陳嘉樺少見與老公賴斯翔一同出現在機場,大有婦唱夫隨的甜蜜氛圍呢~

婚後的陳嘉樺,早就不再是當初的那個“假小子”,如今已經成了一位充滿女人味的好妻子,盤起長發還挺溫柔知性。

不過更多的,還是她骨子里依舊保留的那份幹練與利落,搭配一套灰色西裝把氣質這塊拿捏得死死的,墨鏡遮面儼然一個摩登女強人~

硬朗的西裝外套衣襟大敞,配上同色打底衫簡單豐富層次,同色look看起來也絲毫不枯燥。下搭一條紙袋褲呼應整體配色,寬鬆的褲腿走起路來瀟灑飄逸,自帶鼓風機特效,162cm營造出的強大氣場不可小覷!

本來看見陳嘉樺穿這麼一條紙袋褲也沒什麼,與上衣搭配起來也很貼切,可等我瞧見她老公的時候,才知道在瀟灑率性的風格後面,居然還隱藏着一碗狗糧!因為當天,一同現身的賴斯翔,也穿了條同款長褲,光明正大秀起了恩愛!

不僅如此,賴斯翔也選擇了墨鏡與純色外套的搭配組合,連敞開衣襟的細節都如出一轍,狗糧撒得很有心。

稍長的頭髮還被他綁成了一個小揪揪,配上幾瞥小鬍子痞帥有型。190cm的男模身高更是得天獨厚,邁開長腿走路帶風,滿滿的型格絲毫不比明星差!

於是,當兩人在機場合體,同款灰色紙袋褲甜蜜搶鏡,步伐一致就算了,連風格氣場都神相似,28cm的身高差太有愛~

有這麼一對萌萌的身高差擺在那,兩人哪怕不穿一樣的單品,照樣能吸引不少路人的眼球。前幾天夫妻倆也一塊在機場現身,只是換個內搭和長褲,風格看上去立馬休閑許多。

雖說穿的不是同款,可他倆依舊保持着甜蜜的情侶造型,內搭連帽衫隔空組成黑白配,修身牛仔褲與闊腿褲遙相呼應,臉上架着太陽鏡、腳下雙運動鞋,呈現出“最萌身高差”甜化我!

一塊度假也要打扮得默契滿滿。陳嘉樺條紋襯衣搭配棕色闊腿褲,溫柔的色調舒適居家;高大帥氣的老公就更不用說了,把藍色穿在下半身,白色衛衣無疑與妻子的內搭聯繫,從身後環住Ella,姿勢溫柔又寵溺。

如今,兩人愛情的結晶勁寶也已經兩歲多了,一家三口溫馨同框,喜慶的紅色相得益彰,洋氣的條紋與格紋元素營造趣味。在孩子面前,Ella與老公還不忘穿相近色調的闊腿褲進行配合,又是一位從小“吃狗糧”長大的寶寶呀!

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※網友分享與台中外送茶店搞笑趣事

感情挽回就從三步驟做起

※你應該要知道的電子煙懶人包!

和合術真的可以挽回感情嗎?

※揭開降頭祕辛?

※老司機曝包養內幕