標籤: 電動車

  • ThreadLocal源碼解析-Java8,ThreadLocal的使用場景分析,利用線性探測法解決hash衝突,Java-強引用、軟引用、弱引用、虛引用,利用線性探測法解決hash衝突,分析ThreadLocal的弱引用與內存泄漏問題-Java8

    ThreadLocal源碼解析-Java8,ThreadLocal的使用場景分析,利用線性探測法解決hash衝突,Java-強引用、軟引用、弱引用、虛引用,利用線性探測法解決hash衝突,分析ThreadLocal的弱引用與內存泄漏問題-Java8

    目錄

    一.ThreadLocal介紹

      1.1 ThreadLocal的功能

      1.2 ThreadLocal使用示例

    二.源碼分析-ThreadLocal

      2.1 ThreadLocal的類層級關係

      2.2 ThreadLocal的屬性字段

      2.3 創建ThreadLocal對象

      2.4 ThreadLocal-set操作

      2.5 ThreadLocal-get操作

      2.6 ThreadLocal-remove操作

    三.ThreadLocalMap類

      3.0 線性探測算法解決hash衝突

      3.1 Entry內部類

      3.2 ThreadLocalMap的常量介紹

      3.3 實例化ThreadLocalMap

      3.4 ThreadLocalMap的set操作

      3.5 清理陳舊Entry和rehash

    四.總結 

     

    一.介紹ThreadLocal

    1.1ThreadLocal的功能

      我們知道,變量從作用域範圍進行分類,可以分為“全局變量”、“局部變量”兩種:

      1.全局變量(global variable),比如類的靜態屬性(加static關鍵字),在類的整個生命周期都有效;

      2.局部變量(local variable),比如在一個方法中定義的變量,作用域只是在當前方法內,方法執行完畢后,變量就銷毀(釋放)了;

      使用全局變量,當多個線程同時修改靜態屬性,就容易出現併發問題,導致臟數據;而局部變量一般來說不會出現併發問題(在方法中開啟多線程併發修改局部變量,仍可能引起併發問題);

      再看ThreadLocal,可以用來保存局部變量,只不過這個“局部”是指“線程”作用域,也就是說,該變量在該線程的整個生命周期中有效。

      關於ThreadLocal的使用場景,可以查看ThreadLocal的使用場景分析。

     

    1.2ThreadLocal的使用示例

      ThreadLocal使用非常簡單。

    package cn.ganlixin;
    
    import org.junit.Test;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class TestThreadLocal {
    
        private static class Goods {
            public Integer id;
            public List<String> tags;
        }
    
        @Test
        public void testReference() {
            Goods goods1 = new Goods();
            goods1.id = 10;
            goods1.tags = Arrays.asList("healthy", "cheap");
    
            ThreadLocal<Goods> threadLocal = new ThreadLocal<>();
            threadLocal.set(goods1);
    
            Goods goods2 = threadLocal.get();
            System.out.println(goods1); // cn.ganlixin.TestThreadLocal$Goods@1c655221
            System.out.println(goods2); // cn.ganlixin.TestThreadLocal$Goods@1c655221
    
            goods2.id = 100;
            System.out.println(goods1.id);  // 100
            System.out.println(goods2.id);  // 100
    
            threadLocal.remove();
            System.out.println(threadLocal.get()); // null
        }
    
        @Test
        public void test2() {
            // 一個線程中,可以創建多個ThreadLocal對象,多個ThreadLoca對象互不影響
            ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
            ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
            // ThreadLocal存的值默認為null
    
            System.out.println(threadLocal1.get()); // null
    
            threadLocal1.set("this is value1");
            threadLocal2.set("this is value2");
            System.out.println(threadLocal1.get()); // this is value1
            System.out.println(threadLocal2.get());  // this is value2
    
            // 可以重寫initialValue進行設置初始值
            ThreadLocal<String> threadLocal3 = new ThreadLocal<String>() {
                @Override
                protected String initialValue() {
                    return "this is initial value";
                }
            };
            System.out.println(threadLocal3.get()); // this is initial value
        }
    }
    

      

    二.源碼分析-ThreadLocal

    2.1ThreadLocal類層級關係

      

      ThreadLocal類中有一個內部類ThreadLocalMap,這個類特別重要,ThreadLocal的各種操作基本都是圍繞ThreadLocalMap進行的

      對於ThreadLocalMap有來說,它內部定義了一個Entry內部類,有一個table屬性,是一個Entry數組,和HashMap有一些相似的地方,但是ThreadLocalMap和HashMap並沒有什麼關係。

      先大概看一下內存關係圖,不理解也沒關係,看了後面的代碼應該就能理解了:

       

      大概解釋一下,棧中的Thread ref(引用)堆中的Thread對象,Thread對象有一個屬性threadlocals(ThreadLocalMap類型),這個Map中每一項(Entry)的value是ThreadLocal.set()的值,而Map的key則是ThreadLocal對象。

      下面在介紹源碼的時候,會從兩部分進行介紹,先介紹ThreadLocal的常用api,然後再介紹ThreadLocalMap,因為ThreadLocal的api內部其實都是在操作ThreadLocalMap,所以看源碼時一定要知道他們倆之間的關係

     

    2.2ThreadLocal的屬性

      ThreadLocal有3個屬性,主要的功能就是生成ThreadLocal的hash值。

    // threadLocalHashCode用來表示當前ThreadLocal對象的hashCode,通過計算獲得
    private final int threadLocalHashCode = nextHashCode();
    
    // 一個AtomicInteger類型的屬性,功能就是計數,各種操作都是原子性的,在併發時不會出現問題
    private static AtomicInteger nextHashCode = new AtomicInteger();
    
    // hash值的增量,不是隨便指定的,被稱為“黃金分割數”,能讓hash結果均衡分佈
    private static final int HASH_INCREMENT = 0x61c88647;
    
    /**
     * 通過計算,為當前ThreadLocal對象生成一個HashCode
     */
    private static int nextHashCode() {
        // 獲取當前nextHashCode,然後遞增HASH_INCREMENT
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    

      

    2.3創建ThreadLocal對象

      ThreadLocal類,只有一個無參構造器,如果需要是指默認值,則可以重寫initialValue方法:

    public ThreadLocal() {}
    
    /**
     * 初始值默認為null,要設置初始值,只需要設置為方法返回值即可
     *
     * @return ThreadLocal的初始值
     */
    protected T initialValue() {
        return null;
    }
    

      需要注意的是initialValue方法並不會在創建ThreadLocal對象的時候設置初始值,而是延遲執行:當ThreadLocal直接調用get時才會觸發initialValue執行(get之前沒有調用set來設置過值),initialValue方法在後面還會介紹。 

     

    2.4ThreadLocal-set操作

      下面這段代碼只給出了ThreadLocal的set代碼:

    public void set(T value) {
        // 獲取當前線程
        Thread t = Thread.currentThread();
    
        // 獲取當前線程的ThreadLocalMap屬性,ThreadLocal有一個threadLocals屬性(ThreadLocalMap類型)
        ThreadLocalMap map = getMap(t);
    
        if (map != null) {
            // 如果當前線程有關聯的ThreadLocalMap對象,則調用ThreadLocalMap的set方法進行設置
            map.set(this, value);
        } else {
            // 創建一個與當前線程關聯的ThreadLocalMap對象,並設置對應的value
            createMap(t, value);
        }
    }
    
    /**
     * 獲取線程關聯的ThreadLocalMap對象
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
     * 創建ThreadLocalMap
     * @param t          key為當前線程
     * @param firstValue value為ThreadLocal.set的值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

      如果想立即了解ThreadLocalMap的set方法,則可點此跳轉!

     

    2.5ThreadLocal-get操作

      前面說過“重寫ThreadLocal的initialValue方法來設置ThreadLocal的默認值,並不是在創建ThreadLocal的時候執行的,而是在直接get的時候執行的”,看了下面的代碼,就知道這句話的具體含義了,感覺設計很巧妙:

    public T get() {
        // 獲取當前線程
        Thread t = Thread.currentThread();
    
        // 獲取當前線程對象的threadLocals屬性
        ThreadLocalMap map = getMap(t);
    
        // 若當前線程對象的threadLocals屬性不為空(map不為空)
        if (map != null) {
            // 當前ThreadLocal對象作為key,獲取ThreadLocalMap中對應的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
    
            // 如果找到對應的Entry,則證明該線程的該ThreadLocal有值,返回值即可
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T) e.value;
                return result;
            }
        }
    
        // 1.當前線程對象的threadLocals屬性為空(map為空)
        // 2.或者map不為空,但是未在map中查詢到以該ThreadLocal對象為key對應的entry
        // 這兩種情況,都會進行設置初始值,並將初始值返回
        return setInitialValue();
    }
    
    /**
     * 設置ThreadLocal初始值
     *
     * @return 初始值
     */
    private T setInitialValue() {
        // 調用initialValue方法,該方法可以在創建ThreadLocal的時候重寫
        T value = initialValue();
        Thread t = Thread.currentThread();
    
        // 獲取當前線程的threadLocals屬性(map)
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // threadLocals屬性值不為空,則進行調用ThreadLocalMap的set方法
            map.set(this, value);
        } else {
            // 沒有關聯的threadLocals,則創建ThreadLocalMap,並在map中新增一個Entry
            createMap(t, value);
        }
    
        // 返回初始值
        return value;
    }
    
    /**
     * 初始值默認為null,要設置初始值,只需要設置為方法返回值即可
     * 創建ThreadLocal設置默認值,可以覆蓋initialValue方法,initialValue方法不是在創建ThreadLocal時執行,而是這個時候執行
     *
     * @return ThreadLocal的初始值
     */
    protected T initialValue() {
        return null;
    }
    

         

    2.6ThreadLocal-remove操作

      一般是在ThreadLocal對象使用完后,調用ThreadLocal的remove方法,在一定程度上,可以避免內存泄露;

     

    /**
     * 刪除當前線程中threadLocals屬性(map)中的Entry(以當前ThreadLocal為key的)
     */
    public void remove() {
        // 獲取當前線程的threadLocals屬性(ThreadLocalMap)
        ThreadLocalMap m = getMap(Thread.currentThread());
    
        if (m != null) {
            // 調用ThreadLocalMap的remove方法,刪除map中以當前ThreadLocal為key的entry
            m.remove(this);
        }
    }

     

    三.ThreadLocalMap內部類

    3.0 線性探測算法解決hash衝突

      在介紹ThreadLocalMap的之前,強烈建議先了解一下線性探測算法,這是一種解決Hash衝突的方案,如果不了解這個算法就去看ThreadLocalMap的源碼就會非常吃力,會感到莫名其妙。

      鏈接在此:利用線性探測法解決hash衝突

     

    3.1Entry內部類

      ThreadLocalMap是ThreadLocal的內部類,ThreadLocalMap底層使用數組實現,每一個數組的元素都是Entry類型(在ThreadLocalMap中定義的),源碼如下:

    /**
     * ThreadLocalMap中存放的元素類型,繼承了弱引用類
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        // key對應的value,注意key是ThreadLocal類型
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

      ThreadLocalMap和HashMap類似,比較一下:

      a:底層都是使用數組實現,數組元素類型都是內部定義,Java8中,HashMap的元素是Node類型(或者TreeNode類型),ThreadLocalMap中的元素類型是Entry類型;

      b.都是通過計算得到一個值,將這個值與數組的長度(容量)進行與操作,確定Entry應該放到哪個位置;

      c.都有初始容量、負載因子,超過擴容閾值將會觸發擴容;但是HashMap的初始容量、負載因子是可以更改的,而ThreadLocalMap的初始容量和負載因子不可修改;

      注意Entry繼承自WeakReference類,在實例化Entry時,將接收的key傳給父類構造器(也就是WeakReference的構造器),WeakReference構造器又將key傳給它的父類構造器(Reference):

    // 創建Reference對象,接受一個引用
    Reference(T referent) {
        this(referent, null);
    }
    
    // 設置引用
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
    

      關於Java的各種引用,可以參考:Java-強引用、軟引用、弱引用、虛引用

     

    3.2ThreadLocalMap的常量介紹

    // ThreadLocalMap的初始容量
    private static final int INITIAL_CAPACITY = 16;
    
    // ThreadLocalMap底層存數據的數組
    private Entry[] table;
    
    // ThreadLocalMap中元素的個數
    private int size = 0;
    
    // 擴容閾值,當size達到閾值時會觸發擴容(loadFactor=2/3;newCapacity=2*oldCapacity)
    private int threshold; // Default to 0
    

      

    3.3創建ThreadLocalMap對象

      創建ThreadLocalMap,是在第一次調用ThreadLocal的set或者get方法時執行,其中第一次未set值,直接調用get時,就會利用ThreadLocal的初始值來創建ThreadLocalMap。

      ThreadLocalMap內部類的源碼如下:

    /**
     * 初始化一個ThreadLocalMap對象(第一次調用ThreadLocal的set方法時創建),傳入ThreadLocal對象和對應的value
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        // 創建一個Entry數組,容量為16(默認)
        table = new Entry[INITIAL_CAPACITY];
    
        // 計算新增的元素,應該放到數組的哪個位置,根據ThreadLocal的hash值與初始容量進行"與"操作
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    
        // 創建一個Entry,設置key和value,注意Entry中沒有key屬性,key屬性是傳給Entry的父類WeakReference
        table[i] = new Entry(firstKey, firstValue);
    
        // 初始容量為1
        size = 1;
    
        // 設置擴容閾值
        setThreshold(INITIAL_CAPACITY);
    }
    
    /**
     * 設置擴容閾值,接收容量值,負載因子固定為2/3
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

     

    3.4 ThreadLocalMap的set操作

      ThreadLocal的set方法,其實核心就是調用ThreadLocalMap的set方法,set方法的流程比較長

    /**
     * 為當前ThreadLocal對象設置value
     */
    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
    
        // 計算新元素應該放到哪個位置(這個位置不一定是最終存放的位置,因為可能會出現hash衝突)
        int i = key.threadLocalHashCode & (len - 1);
    
        // 判斷計算出來的位置是否被佔用,如果被佔用,則需要找出應該存放的位置
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            // 獲取Entry中key,也就是弱引用的對象
            ThreadLocal<?> k = e.get();
    
            // 判斷key是否相等(判斷弱引用的是否為同一個ThreadLocal對象)如果是,則進行覆蓋
            if (k == key) {
                e.value = value;
                return;
            }
    
            // k為null,也就是Entry的key已經被回收了,當前的Entry是一個陳舊的元素(stale entry)
            if (k == null) {
                // 用新元素替換掉陳舊元素,同時也會清理其他陳舊元素,防止內存泄露
                replaceStaleEntry(key, value, i);
                return;
            }
        }
    
        // map中沒有ThreadLocal對應的key,或者說沒有找到陳舊的Entry,則創建一個新的Entry,放入數組中
        tab[i] = new Entry(key, value);
        // ThreadLocalMap的元素數量加1
        int sz = ++size;
    
        // 先清理map中key為null的Entry元素,該Entry也應該被回收掉,防止內存泄露
        // 如果清理出陳舊的Entry,那麼就判斷是否需要擴容,如果需要的話,則進行rehash
        if (!cleanSomeSlots(i, sz) && sz >= threshold) {
            rehash();
        }
    }

      上面最後幾行代碼涉及到清理陳舊Entry和rehash,這兩塊的代碼在下面。

     

    3.5清理陳舊Entry和rehash

      陳舊的Entry,是指Entry的key為null,這種情況下,該Entry是不可訪問的,但是卻不會被回收,為了避免出現內存泄漏,所以需要在每次get、set、replace時,進行清理陳舊的Entry,下面只給出一部分代碼:

    /**
     * 清理map中key為null的Entry元素,該Entry也應該被回收掉,防止內存泄露
     *
     * @param i 新Entry插入的位置
     * @param n 數組中元素的數量
     * @return 是否有陳舊的entry的清除
     */
    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        do {
            i = nextIndex(i, len);
            Entry e = tab[i];
            if (e != null && e.get() == null) {
                n = len;
                removed = true;
                i = expungeStaleEntry(i);
            }
        } while ((n >>>= 1) != 0);
        return removed;
    }
    
    private void rehash() {
        // 清除底層數組中所有陳舊的(stale)的Entry,也就是key為null的Entry
        // 同時每清除一個Entry,就對其後面的Entry重新計算hash,獲取新位置,使用線性探測法,重新確定最終位置
        expungeStaleEntries();
    
        // 清理完陳舊Entry后,判斷是否需要擴容
        if (size >= threshold - threshold / 4) {
            // 擴容時,容量變為舊容量的2倍,再進行rehash,並使用線性探測發確定Entry的新位置
            resize();
        }
    }
    

      在rehash的時候,涉及到“線性探測法”,是一種用來解決hash衝突的方案,可以查看利用線性探測法解決hash衝突了解詳情。

     

    3.6ThreadLocalMap-remove操作

      remove操作,是調用ThreadLocal.remove()方法時,刪除當前線程的ThreadLocalMap中該ThreadLocal為key的Entry。

    /**
     * 移除當前線程的threadLocals屬性中key為ThreadLocal的Entry
     *
     * @param key 要移除的Entry的key(ThreadLocal對象)
     */
    private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
    
        // 計算出該ThreadLocal對應的key應該存放的位置
        int i = key.threadLocalHashCode & (len - 1);
    
        // 找到指定位置,開始按照線性探測算法進行查找到該Thread的Entry
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
    
            // 如果Entry的key相同
            if (e.get() == key) {
                // 調用WeakReference的clear方法,Entry的key是弱引用,指向ThreadLocal,現在將key指向null
                // 則該ThreadLocal對象在會在下一次gc時,被垃圾收集器回收
                e.clear();
    
                // 將該位置的Entry中的value置為null,於是value引用的對象也會被垃圾收集器回收(不會造成內存泄漏)
                // 同時內部會調整Entry的順序(開放探測算法的特點,刪除元素後會重新調整順序)
                expungeStaleEntry(i);
    
                return;
            }
        }
    }

     

    四.總結

      在學習ThreadLocal類源碼的過程還是受益頗多的:

      1.ThreadLocal的使用場景;

      2.initialValue的延遲執行;

      3.HashMap使用鏈表+紅黑樹解決hash衝突,ThreadLocalMap使用線性探測算法(開放尋址)解決hash衝突

      另外,ThreadLocal還有一部分內容,是關於弱引用和內存泄漏的問題,可以參考:分析ThreadLocal的弱引用與內存泄漏問題-Java8。

     

      原文地址:https://www.cnblogs.com/-beyond/p/13093032.html

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 線上服務的FGC問題排查,看這篇就夠了!

    線上服務的FGC問題排查,看這篇就夠了!

    線上服務的GC問題,是Java程序非常典型的一類問題,非常考驗工程師排查問題的能力。同時,幾乎是面試必考題,但是能真正答好此題的人並不多,要麼原理沒吃透,要麼缺乏實戰經驗。

    過去半年時間里,我們的廣告系統出現了多次和GC相關的線上問題,有Full GC過於頻繁的,有Young GC耗時過長的,這些問題帶來的影響是:GC過程中的程序卡頓,進一步導致服務超時從而影響到廣告收入。

    這篇文章,我將以一個FGC頻繁的線上案例作為引子,詳細介紹下GC的排查過程,另外會結合GC的運行原理給出一份實踐指南,希望對你有所幫助。內容分成以下3個部分:

    1、從一次FGC頻繁的線上案例說起

    2、GC的運行原理介紹

    3、排查FGC問題的實踐指南

    01 從一次FGC頻繁的線上案例說起

    去年10月份,我們的廣告召回系統在程序上線后收到了FGC頻繁的系統告警,通過下面的監控圖可以看到:平均每35分鐘就進行了一次FGC。而程序上線前,我們的FGC頻次大概是2天一次。下面,詳細介紹下該問題的排查過程。

    1. 檢查JVM配置

    通過以下命令查看JVM的啟動參數:
    ps aux | grep “applicationName=adsearch”

    -Xms4g -Xmx4g -Xmn2g -Xss1024K
    -XX:ParallelGCThreads=5
    -XX:+UseConcMarkSweepGC
    -XX:+UseParNewGC
    -XX:+UseCMSCompactAtFullCollection
    -XX:CMSInitiatingOccupancyFraction=80

    可以看到堆內存為4G,新生代為2G,老年代也為2G,新生代採用ParNew收集器,老年代採用併發標記清除的CMS收集器,當老年代的內存佔用率達到80%時會進行FGC。

    進一步通過 jmap -heap 7276 | head -n20 可以得知新生代的Eden區為1.6G,S0和S1區均為0.2G。

    2. 觀察老年代的內存變化

    通過觀察老年代的使用情況,可以看到:每次FGC后,內存都能回到500M左右,因此我們排除了內存泄漏的情況。

    3. 通過jmap命令查看堆內存中的對象

    通過命令 jmap -histo 7276 | head -n20

    上圖中,按照對象所佔內存大小排序,显示了存活對象的實例數、所佔內存、類名。可以看到排名第一的是:int[],而且所佔內存大小遠遠超過其他存活對象。至此,我們將懷疑目標鎖定在了 int[] .

    4. 進一步dump堆內存文件進行分析

    鎖定 int[] 后,我們打算dump堆內存文件,通過可視化工具進一步跟蹤對象的來源。考慮堆轉儲過程中會暫停程序,因此我們先從服務管理平台摘掉了此節點,然後通過以下命令dump堆內存:

    jmap -dump:format=b,file=heap 7276

    通過JVisualVM工具導入dump出來的堆內存文件,同樣可以看到各個對象所佔空間,其中int[]佔到了50%以上的內存,進一步往下便可以找到 int[] 所屬的業務對象,發現它來自於架構團隊提供的codis基礎組件。

    5. 通過代碼分析可疑對象

    通過代碼分析,codis基礎組件每分鐘會生成約40M大小的int數組,用於統計TP99 和 TP90,數組的生命周期是一分鐘。而根據第2步觀察老年代的內存變化時,發現老年代的內存基本上也是每分鐘增加40多M,因此推斷:這40M的int數組應該是從新生代晉陞到老年代。

    我們進一步查看了YGC的頻次監控,通過下圖可以看到大概1分鐘有8次左右的YGC,這樣基本驗證了我們的推斷:因為CMS收集器默認的分代年齡是6次,即YGC 6次后還存活的對象就會晉陞到老年代,而codis組件中的大數組生命周期是1分鐘,剛好滿足這個要求。

    至此,整個排查過程基本結束了,那為什麼程序上線前沒出現此問題呢?通過上圖可以看到:程序上線前YGC的頻次在5次左右,此次上線后YGC頻次變成了8次左右,從而引發了此問題。

    6. 解決方案

    為了快速解決問題,我們將CMS收集器的分代年齡改成了15次,改完后FGC頻次恢復到了2天一次,後續如果YGC的頻次超過每分鐘15次還會再次觸發此問題。當然,我們最根本的解決方案是:優化程序以降低YGC的頻率,同時縮短codis組件中int數組的生命周期,這裏就不做展開了。

    02 GC的運行原理介紹

    上面整個案例的分析過程中,其實涉及到很多GC的原理知識,如果不懂得這些原理就着手處理,其實整個排查過程是很抓瞎的。

    這裏,我選擇幾個最核心的知識點,展開介紹下GC的運行原理,最後再給出一份實踐指南。

    1. 堆內存結構

    大家都知道: GC分為YGC和FGC,它們均發生在JVM的堆內存上。先來看下JDK8的堆內存結構:

    可以看到,堆內存採用了分代結構,包括新生代和老年代。新生代又分為:Eden區,From Survivor區(簡稱S0),To Survivor區(簡稱S1區),三者的默認比例為8:1:1。另外,新生代和老年代的默認比例為1:2。

    堆內存之所以採用分代結構,是考慮到絕大部分對象都是短生命周期的,這樣不同生命周期的對象可放在不同的區域中,然後針對新生代和老年代採用不同的垃圾回收算法,從而使得GC效率最高。

    2. YGC是什麼時候觸發的?

    大多數情況下,對象直接在年輕代中的Eden區進行分配,如果Eden區域沒有足夠的空間,那麼就會觸發YGC(Minor GC),YGC處理的區域只有新生代。因為大部分對象在短時間內都是可收回掉的,因此YGC后只有極少數的對象能存活下來,而被移動到S0區(採用的是複製算法)。

    當觸發下一次YGC時,會將Eden區和S0區的存活對象移動到S1區,同時清空Eden區和S0區。當再次觸發YGC時,這時候處理的區域就變成了Eden區和S1區(即S0和S1進行角色交換)。每經過一次YGC,存活對象的年齡就會加1。

    3. FGC又是什麼時候觸發的?

    下面4種情況,對象會進入到老年代中:

    1、YGC時,To Survivor區不足以存放存活的對象,對象會直接進入到老年代。

    2、經過多次YGC后,如果存活對象的年齡達到了設定閾值,則會晉陞到老年代中。

    3、動態年齡判定規則,To Survivor區中相同年齡的對象,如果其大小之和佔到了 To Survivor區一半以上的空間,那麼大於此年齡的對象會直接進入老年代,而不需要達到默認的分代年齡。

    4、大對象:由-XX:PretenureSizeThreshold啟動參數控制,若對象大小大於此值,就會繞過新生代, 直接在老年代中分配。

    當晉陞到老年代的對象大於了老年代的剩餘空間時,就會觸發FGC(Major GC),FGC處理的區域同時包括新生代和老年代。除此之外,還有以下4種情況也會觸發FGC:

    1、老年代的內存使用率達到了一定閾值(可通過參數調整),直接觸發FGC。

    2、空間分配擔保:在YGC之前,會先檢查老年代最大可用的連續空間是否大於新生代所有對象的總空間。如果小於,說明YGC是不安全的,則會查看參數 HandlePromotionFailure 是否被設置成了允許擔保失敗,如果不允許則直接觸發Full GC;如果允許,那麼會進一步檢查老年代最大可用的連續空間是否大於歷次晉陞到老年代對象的平均大小,如果小於也會觸發 Full GC。

    3、Metaspace(元空間)在空間不足時會進行擴容,當擴容到了-XX:MetaspaceSize 參數的指定值時,也會觸發FGC。

    4、System.gc() 或者Runtime.gc() 被顯式調用時,觸發FGC。

    4. 在什麼情況下,GC會對程序產生影響?

    不管YGC還是FGC,都會造成一定程度的程序卡頓(即Stop The World問題:GC線程開始工作,其他工作線程被掛起),即使採用ParNew、CMS或者G1這些更先進的垃圾回收算法,也只是在減少卡頓時間,而並不能完全消除卡頓。

    那到底什麼情況下,GC會對程序產生影響呢?根據嚴重程度從高到底,我認為包括以下4種情況:

    1、FGC過於頻繁:FGC通常是比較慢的,少則幾百毫秒,多則幾秒,正常情況FGC每隔幾個小時甚至幾天才執行一次,對系統的影響還能接受。但是,一旦出現FGC頻繁(比如幾十分鐘就會執行一次),這種肯定是存在問題的,它會導致工作線程頻繁被停止,讓系統看起來一直有卡頓現象,也會使得程序的整體性能變差。

    2、YGC耗時過長:一般來說,YGC的總耗時在幾十或者上百毫秒是比較正常的,雖然會引起系統卡頓幾毫秒或者幾十毫秒,這種情況幾乎對用戶無感知,對程序的影響可以忽略不計。但是如果YGC耗時達到了1秒甚至幾秒(都快趕上FGC的耗時了),那卡頓時間就會增大,加上YGC本身比較頻繁,就會導致比較多的服務超時問題。

    3、FGC耗時過長:FGC耗時增加,卡頓時間也會隨之增加,尤其對於高併發服務,可能導致FGC期間比較多的超時問題,可用性降低,這種也需要關注。

    4、YGC過於頻繁:即使YGC不會引起服務超時,但是YGC過於頻繁也會降低服務的整體性能,對於高併發服務也是需要關注的。

    其中,「FGC過於頻繁」和「YGC耗時過長」,這兩種情況屬於比較典型的GC問題,大概率會對程序的服務質量產生影響。剩餘兩種情況的嚴重程度低一些,但是對於高併發或者高可用的程序也需要關注。

    03 排查FGC問題的實踐指南

    通過上面的案例分析以及理論介紹,再總結下FGC問題的排查思路,作為一份實踐指南供大家參考。

    1. 清楚從程序角度,有哪些原因導致FGC?

    1、大對象:系統一次性加載了過多數據到內存中(比如SQL查詢未做分頁),導致大對象進入了老年代。

    2、內存泄漏:頻繁創建了大量對象,但是無法被回收(比如IO對象使用完后未調用close方法釋放資源),先引發FGC,最後導致OOM.

    3、程序頻繁生成一些長生命周期的對象,當這些對象的存活年齡超過分代年齡時便會進入老年代,最後引發FGC. (即本文中的案例)

    4、程序BUG導致動態生成了很多新類,使得 Metaspace 不斷被佔用,先引發FGC,最後導致OOM.

    5、代碼中顯式調用了gc方法,包括自己的代碼甚至框架中的代碼。

    6、JVM參數設置問題:包括總內存大小、新生代和老年代的大小、Eden區和S區的大小、元空間大小、垃圾回收算法等等。

    2. 清楚排查問題時能使用哪些工具

    1、公司的監控系統:大部分公司都會有,可全方位監控JVM的各項指標。

    2、JDK的自帶工具,包括jmap、jstat等常用命令:

    查看堆內存各區域的使用率以及GC情況
    jstat -gcutil -h20 pid 1000

    查看堆內存中的存活對象,並按空間排序
    jmap -histo pid | head -n20

    dump堆內存文件
    jmap -dump:format=b,file=heap pid

    3、可視化的堆內存分析工具:JVisualVM、MAT等

    3. 排查指南

    1、查看監控,以了解出現問題的時間點以及當前FGC的頻率(可對比正常情況看頻率是否正常)

    2、了解該時間點之前有沒有程序上線、基礎組件升級等情況。

    3、了解JVM的參數設置,包括:堆空間各個區域的大小設置,新生代和老年代分別採用了哪些垃圾收集器,然後分析JVM參數設置是否合理。

    4、再對步驟1中列出的可能原因做排除法,其中元空間被打滿、內存泄漏、代碼顯式調用gc方法比較容易排查。

    5、針對大對象或者長生命周期對象導致的FGC,可通過 jmap -histo 命令並結合dump堆內存文件作進一步分析,需要先定位到可疑對象。

    6、通過可疑對象定位到具體代碼再次分析,這時候要結合GC原理和JVM參數設置,弄清楚可疑對象是否滿足了進入到老年代的條件才能下結論。

    04 最後的話

    這篇文章通過線上案例並結合GC原理詳細介紹了FGC的排查過程,同時給出了一份實踐指南。

    後續會以類似的方式,再分享一個YGC耗時過長的案例,希望能幫助大家吃透GC問題排查,如果覺得本文對你有幫助,請大家關注我的個人公眾號!

    – End –

    作者簡介:程序員,985碩士,前亞馬遜Java工程師,現58轉轉技術總監。持續分享技術和管理方向的文章。如果感興趣,可微信掃描下面的二維碼關注我的公眾號:『IT人的職場進階』

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 雀巢號召新創尖兵 加速開發乳製品替代品

    摘錄自2020年9月29日中央社報導

    瑞士食品業巨擘雀巢集團(Nestle)今(29日)發表聲明稿說:「公司擬將旗下位於瑞士科諾爾芬根(Konolfingen)的研發中心,開放給新創公司、學生和科學家。」,加速開發以植物為主的乳製品替代品。

    雀巢表示,將會有內部、外部以及混合編組團隊在研發中心工作,為期六個月。

    除了對永續乳製品進行測試外,集團也計畫鼓勵開發以植物為基礎的乳製品替代品。雀巢發表以此程序研發出來的一種使用蔬菜為基礎乳品。

    氣候變遷
    國際新聞
    瑞士
    乳製品
    素食

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • Python 圖像處理 OpenCV (10):圖像處理形態學之頂帽運算與黑帽運算

    Python 圖像處理 OpenCV (10):圖像處理形態學之頂帽運算與黑帽運算

    前文傳送門:

    「Python 圖像處理 OpenCV (1):入門」

    「Python 圖像處理 OpenCV (2):像素處理與 Numpy 操作以及 Matplotlib 显示圖像」

    「Python 圖像處理 OpenCV (3):圖像屬性、圖像感興趣 ROI 區域及通道處理」

    「Python 圖像處理 OpenCV (4):圖像算數運算以及修改顏色空間」

    「Python 圖像處理 OpenCV (5):圖像的幾何變換」

    「Python 圖像處理 OpenCV (6):圖像的閾值處理」

    「Python 圖像處理 OpenCV (7):圖像平滑(濾波)處理」

    「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」

    「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」

    引言

    今天是圖形處理形態學的最後一篇,我們介紹頂帽運算和黑帽運算。

    建議先閱讀前面兩篇圖像處理的內容:

    「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」

    「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」

    形態學之頂帽運算

    圖像處理頂帽運算是一個獲取圖像噪聲的運算,它是由原始圖像減去圖像開運算而得到的結果:

    頂帽運算 = 原始圖像 - 開運算
    

    圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_TOPHAT ,示例如下:

    import cv2 as cv
    import numpy as np
    import matplotlib.pyplot as plt
    
    # 讀取圖片
    source = cv.imread("demo_noise_white.jpg", cv.IMREAD_GRAYSCALE)
    
    # 設置卷積核
    kernel = np.ones((5, 5), np.uint8)
    
    # 開運算
    open = cv.morphologyEx(source, cv.MORPH_OPEN, kernel)
    
    # 頂帽運算
    dst = cv.morphologyEx(source, cv.MORPH_TOPHAT, kernel)
    
    # 显示結果
    titles = ['Source Img','Open Img', 'Tophat Img']
    images = [source, open, dst]
    
    # matplotlib 繪圖
    for i in range(3):
       plt.subplot(1, 3, i+1), plt.imshow(images[i],'gray')
       plt.title(titles[i])
       plt.xticks([]),plt.yticks([])
    
    plt.show()
    

    形態學之黑帽運算

    圖像處理頂帽運算是一個獲取圖像內部的小孔,或者前景色中的小黑點的運算。

    它是由圖像閉運算減去原始圖像的操作:

    黑帽運算 = 閉運算圖像 - 原始圖像
    

    圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_BLACKHAT ,示例如下:

    import cv2 as cv
    import numpy as np
    import matplotlib.pyplot as plt
    
    # 讀取圖片
    source = cv.imread("demo_noise_black.jpg", cv.IMREAD_GRAYSCALE)
    
    # 設置卷積核
    kernel = np.ones((5, 5), np.uint8)
    
    # 黑帽運算
    dst = cv.morphologyEx(source, cv.MORPH_BLACKHAT, kernel)
    
    # 構造显示結果數組
    titles = ['Source Img', 'Black Img']
    images = [source, dst]
    
    # matplotlib 繪圖
    for i in range(2):
       plt.subplot(1, 2, i+1), plt.imshow(images[i],'gray')
       plt.title(titles[i])
       plt.xticks([]),plt.yticks([])
    
    plt.show()
    

    今天的內容比較短,至此,圖像形態學的幾個基礎的運算已經全部介紹完畢,希望各位同學能理解這幾個運算的原理,而不是僅僅知道了幾個參數或者說幾個方法的調用。

    示例代碼

    如果有需要獲取源碼的同學可以在公眾號回復「OpenCV」進行獲取。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 自己動手實現深度學習框架-8 RNN文本分類和文本生成模型

    自己動手實現深度學習框架-8 RNN文本分類和文本生成模型

    代碼倉庫: https://github.com/brandonlyg/cute-dl

    目標

            上階段cute-dl已經可以構建基礎的RNN模型。但對文本相模型的支持不夠友好, 這個階段的目標是, 讓框架能夠友好地支持文本分類和本文生成任務。具體包括:

    1. 添加嵌入層, 為文本尋找高效的向量表示。
    2. 添加類別抽樣函數, 根據模型輸出的類別分佈抽樣得到生成的文本。
    3. 使用imdb-review數據集驗證文本分類模型。
    4. 使用一個古詩數據集驗證文本生成模型。

            這階段涉及到的代碼比較簡單因此接下來會重點描述RNN語言相關模型中涉及到的數學原理和工程方法。

    數學原理

    文本分類模型

            可以把文本看成是一個詞的序列\(W=[w_1, w_2, …, w_T]\), 在訓練數據集中每個文本屬於一個類別\(a_i\), \(a_i∈A\), 集合 \(A = \{ a_1, a_2, …, a_k \}\) 是一個類別別集合. 分類模型要做的是給定一個文本W, 計算所有類別的后驗概率:

    \[P(a_i|W) = P(a_i|w_1,w_2,…,w_T), \quad i=1,2,…k \]

            那麼文本序列W的類別為:

    \[a = arg \max_{a_i} P(a_i|w_1,w_2,…,w_T) \]

            即在給定文本的條件下, 具有最大后驗概率的類別就是文本序列W所屬的類別.

    文本預測模型

            設任意一個文本序列為\(W=[w_1,w_2,…,W_T]\), 任意一個詞\(w_i ∈ V\), V是所有詞彙的集合,也叫詞彙表, 這裏需要強調的是\(w_i\)在V中是無序的, 但在W中是有序的, 文本預測的任務是, 計算任意一個詞\(w_i ∈ V\)在給定一個序列中的任意一個位置出現的概率:

    \[P(w_1,…,W_T) = ∏_{t=1}^T P(w_t|w_1,…,w_{t-1}) \]

            文本預測輸出一個\(w_i ∈ V\)的分佈列, 根據這個分佈列從V中抽取一個詞即為預測結果。不同於分類任務,這裏不是取概率最大的詞, 這裏的預測結果是某個詞出現的在一個序列特定位置的個概率,只要概率不是0都有可能出現,所以要用抽樣的方法確定某次預測的結果。

    詞的数字化表示

            任意一條數據在送入模型之前都要表示為一個数字化的向量, 文本數據也不例外。一個文本可以看成詞的序列,因此只要把詞数字化了,文本自然也就数字化了。對於詞來說,最簡單的方式是用詞在詞彙表中的唯一ID來表示, ID需要遵守兩個最基本的規則:

    1. 每個詞的ID在詞彙表中必須是唯一的.
    2. 每個詞的ID一旦確定不能變化.

            這種表示很難表達詞之間的關係, 例如: 在詞彙表中把”好”的ID指定為100, 如果希望ID能夠反映詞意的關係, 需要把”好”的近意詞: “善”, “美”, “良”, “可以”編碼為98, 99, 101, 102. 目前為止這看起還行. 如果還希望ID能夠反映詞之間的語法關係, “好”前後經常出現的詞: “友”, “人”, “的”, 這幾個詞的ID就很難選擇, 不論怎樣, 都會發現兩個詞它們在語義和語法上的關係都很遠,但ID卻很接近。這也說明了標量的表達能力很有限,無法表達多個維度的關係。為了能夠表達詞之間多個維度的的關係,多維向量是一個很好的選擇. 向量之間的夾大小衡量它們之間的關係:

    \[cos(θ) = \frac{<A, B>}{|A||B|} \]

            對於兩個向量A, B使用它們的點積, 模的乘積就能得到夾角θ餘弦值。當cos(θ)->1表示兩個向量的相似度高, cos(θ)->0 表示兩個向量是不相關的, cos(θ)->-1 表示兩個向量是相反的。

            把詞的ID轉換成向量,最簡單的辦法是使用one-hot編碼, 這樣得到的向量有兩個問題:

    1. 任意兩個向量A,B, <A,B>=0, 夾角的餘弦值cos(θ)=0, 不能表達詞之間的關係.
    2. 向量的維度等於詞彙表的大小, 而且是稀疏向量,這和導致模型有大量的參數,模型訓練過程的運算量也很大.

            詞嵌入技術就是為解決詞表示的問題而提出的。詞嵌入把詞ID映射到一個合適維度的向量空間中, 在這個向量空間中為每個ID分配一個唯一的向量, 把這些向量當成參數看待, 在特定任務的模型中學習這些參數。當模型訓練完成后, 這些向量就是詞在這個特定任務中的一個合適的表示。詞嵌入向量的訓練步驟有:

    1. 收集訓練數據集中的詞彙, 構建詞彙表。
    2. 為詞彙表中的每個詞分配一個唯一的ID。假設詞彙表中的詞彙量是N, 詞ID的取值為:0,1,2,…,N-1, 對人任意一個0<ID<N-1, 必然存在ID-1, ID+1.
    3. 隨機初始化N個D維嵌入向量, 向量的索引為0,1,2,…,N-1. 這樣詞ID就成了向量的索引.
    4. 定義一個模型, 把嵌入向量作為模型的輸入層參与訓練.
    5. 訓練模型.

    嵌入層實現

            代碼: cutedl/rnn_layers.py, Embedding類.

            初始化嵌入向量, 嵌入向量使用(-1, 1)區間均勻分佈的隨機變量初始化:

    '''
    dims 嵌入向量維數
    vocabulary_size 詞彙表大小
    need_train 是否需要訓練嵌入向量
    '''
    def __init__(self, dims, vocabulary_size, need_train=True):
        #初始化嵌入向量
        initializer = self.weight_initializers['uniform']
        self.__vecs = initializer((vocabulary_size, dims))
    
        super().__init__()
    
        self.__params = None
        if need_train:
            self.__params = []
            self.__cur_params = None
            self.__in_batch = None
    

            初始化層參數時把所有的嵌入向量變成參与訓練的參數:

    def init_params(self):
        if self.__params is None:
            return
    
        voc_size, _ = self.__vecs.shape
        for i in range(voc_size):
            pname = 'weight_%d'%i
            p = LayerParam(self.name, pname, self.__vecs[i])
            self.__params.append(p)
    

            向前傳播時, 把形狀為(m, t)的數據轉換成(m, t, n)形狀的數據, 其中t是序列長度, n是嵌入向量的維數.

    '''
    in_batch shape=(m, T)
    return shape (m, T, dims)
    '''
    def forward(self, in_batch, training):
        m,T = in_batch.shape
        outshape = (m, T, self.outshape[-1])
        out = np.zeros(outshape)
    
        #得到每個序列的嵌入向量表示
        for i in range(m):
            out[i] = self.__vecs[in_batch[i]]
    
        if training and self.__params is not None:
            self.__in_batch = in_batch
    
        return out
    

            反向傳播時只關注當前批次使用到的向量, 注意同一個向量可能被多次使用, 需要累加同一個嵌入向量的梯度.

    def backward(self, gradient):
        if self.__params is None:
            return
    
        #pdb.set_trace()
        in_batch = self.__in_batch
        params = {}
        m, T, _ = gradient.shape
        for i in range(m):
            for t in range(T):
                grad = gradient[i, t]
                idx = self.__in_batch[i, t]
    
                #更新當前訓練批次的梯度
                if idx not in params:
                    #當前批次第一次發現該嵌入向量
                    params[idx] = self.__params[idx]
                    params[idx].gradient = grad
                else:
                    #累加當前批次梯度
                    params[idx].gradient += grad
    
        self.__cur_params = list(params.values())
    

    驗證

    imdb-review數據集上的分類模型

            代碼: examples/rnn/text_classify.py.

            數據集下載地址: https://pan.baidu.com/s/13spS_Eac_j0uRvCVi7jaMw 密碼: ou26

    數據集處理

            數據集處理時有幾個需要注意的地方:

    1. imdb-review數據集由長度不同的文本構成, 送入模型的數據形狀為(m, t, n), 至少要求一個批次中的數據具有相同的序列長度, 因此在對數據進行分批時, 對數據按批次填充.
    2. 一般使用0為填充編碼. 在構建詞彙表時, 假設有v個詞彙, 詞彙的編碼為1,2,…,v.
    3. 由於對文本進行分詞, 編碼比較耗時。可以把編碼后的數據保存起來,作為數據集的預處理數據, 下次直接加載使用。

    模型

    def fit_gru():
        print("fit gru")
        model = Model([
                    rnn.Embedding(64, vocab_size+1),
                    wrapper.Bidirectional(rnn.GRU(64), rnn.GRU(64)),
                    nn.Filter(),
                    nn.Dense(64),
                    nn.Dropout(0.5),
                    nn.Dense(1, activation='linear')
                ])
        model.assemble()
        fit('gru', model)
    

            訓練報告:

    這個模型和tensorflow給出的模型略有差別, 少了一個RNN層wrapper.Bidirectional(rnn.GRU(32), rnn.GRU(32)), 這個模型經過16輪的訓練達到了tensorflow模型的水平.

    文本生成模型

            我自己收集了一個古由詩詞構成的小型數據集, 用來驗證文本生成模型. 代碼: examples/rnn/text_gen.py.

            數據集下載地址: https://pan.baidu.com/s/14oY_wol0d9hE_9QK45IkzQ 密碼: 5f3c

            模型定義:

    def fit_gru():
        vocab_size = vocab.size()
        print("vocab size: ", vocab_size)
        model = Model([
                    rnn.Embedding(256, vocab_size),
                    rnn.GRU(1024, stateful=True),
                    nn.Dense(1024),
                    nn.Dropout(0.5),
                    nn.Dense(vocab_size, activation='linear')
                ])
    
        model.assemble()
        fit("gru", model)
    

            訓練報告:

            生成七言詩:

    def gen_text():
        mpath = model_path+"gru"
    
        model = Model.load(mpath)
        print("loadding model finished")
        outshape = (4, 7)
    
        print("vocab size: ", vocab.size())
    
        def do_gen(txt):
            #編碼
            #pdb.set_trace()
            res = vocab.encode(sentence=txt)
    
            m, n = outshape
    
            for i in range(m*n - 1):
                in_batch = np.array(res).reshape((1, -1))
                preds = model.predict(in_batch)
                #取最後一維的預測結果
                preds = preds[:, -1]
                outs = dlmath.categories_sample(preds, 1)
                res.append(outs[0,0])
    
            #pdb.set_trace()
            txt = ""
            for i in range(m):
                txt = txt + ''.join(vocab.decode(res[i*n:(i+1)*n])) + "\n"
    
            return txt
    
    
        starts = ['雲', '故', '畫', '花']
        for txt in starts:
            model.reset()
            res = do_gen(txt)
            print(res)
    

            生成的文本:

    雲填纜首月悠覺
    纜濯醉二隱隱白
    湖杖雨遮雙雨鄉
    焉秣都滄楓寓功
    
    故民民時都人把
    陳雨積存手菜破
    好纜簾二龍藕卻
    趣晚城矣中村桐
    
    畫和春覺上蓋騎
    滿楚事勝便京兵
    肯霆唇恨朔上楊
    志月隨肯八焜著
    
    花夜維他客陳月
    客到夜狗和悲布
    關欲摻似瓦闊靈
    山商過牆灘幽惘
    

            是不是很像李商隱的風格?

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 豐田奕澤怎麼樣?讓它的設計師來解答這個問題吧

    豐田奕澤怎麼樣?讓它的設計師來解答這個問題吧

    具體的策略,用我的概來概括就是“非常6+1”,六個策略+一個特別關注的事情,具體分為品牌策略、車型策略、網絡策略、區域策略、經銷商盈利策略,一個就是跟經銷店溝通支持,提供快速解決。通過非常6+1的落實,Q1結果出來,銷售了16萬,加上關閉了的4款車型,去年同比增長11%,應該說順利實現開門紅。

    如果你要問我一汽豐田近期最受關注的一款車是什麼,我絕對會毫不猶豫告訴你——奕澤,作為一汽豐田旗下首款小型SUV,奕澤憑藉設計犀利的造型、雙色的車身等諸多亮點吸引了不少人的關注,而這一次北京車展上奕澤也正式亮相,雖然並沒有上市,但是距離奕澤馳騁在中國大街小巷那一天已經比較近了,而這一次北京車展我們有幸採訪到一汽豐田汽車銷售有限公司的田青久先生以及一汽豐田銷售常務副總經理水谷雅史先生,一起看看他們能夠給我們分享哪些關於奕澤以及一汽豐田的秘密吧!

    主題:一汽豐田專訪

    時間:2018年4月25日下午2:30

    地點:中國國際會展中心E1

    受訪人:一汽豐田汽車銷售有限公司 田青久先生

    一汽豐田銷售常務副總經理水谷雅史先生

    一汽豐田銷售常務副總經理水谷雅史先生

    提問:一汽豐田的各位領導好,我想請問一下炫酷的奕澤6月份上市,有哪些亮點抓住用戶呢?

    田青久:他的亮點很多:首先,在基礎的駕駛性能上,無論是駕趣、拐彎、剎車技術性,均達到世界領先的水平。前期在珠海,我們經銷商進行的對比試駕活動,我也參与了試駕,確實有着改朝換代的感覺,跟其他的SUV完全不同,我們將儘快的安排在座的媒體老師體驗這款車。

    說起來亮點幾大方面:第一,奕動美學的設計,顏值即正義,顏值用太多的話無法說清楚,只有大家真正感受,用我的話就是一見傾心,心動不已;二見鍾情,買它回家。中看不中用也不行,是吧?首先,鑽石動力組合,搭載了Dynamic Force Engine 2.0L “噴汽流控發動機”加上10速變動器。為什麼是鑽石動力組合,它的性能達到了非常高的水平,126千瓦,熱效率達到世界領先40%。這是從中用的角度來說。中看中用之後,安全不安全呢?在安全配置上從起步車型開始,全系搭載了豐田智行安全系統(TSS系統)和10氣囊,媒體老師都很了解,這種配置一般只在豪華車型上會配備。所以,概括來說就是前所未有的顏值,前所未有的駕趣,前所未有的安全。

    提問:伴隨着消費升級,一汽豐田2018年會進行怎麼樣的調整,來應對中國汽車市場的變化?

    水谷雅史:首先感謝您對一汽豐田的關心,感謝您的提問。接下來我來回答一下您的問題。

    首先,中國市場在飛速的變化發展,這是我的認知。通過這一次的車展,我們看到新的國產車不斷出現,以及新的品牌不斷出現,新的市場在充實。同時,我們也感覺危機感。所以,我想未來的汽車市場外資品牌與純國資品牌的競爭會日趨激烈,這是毋庸置疑的。所以,在這樣的競爭環境下,各大企業必須要提升自己商品的魅力與實力才可以。

    為此,從今年開始我們開始導入了TNGA豐巢概念下的新車型。昨天大家看到奕澤IZOA這款車型,在外觀、環境貢獻能力、安全都有進化和提升,商品大幅進化,就是激烈的市場,帶給我們的價值。

    第二,我們經銷店給我們客戶提供什麼樣的服務,在未來是越來越重要的。未來,我們將吸取海外的服務經驗,將它們引進國內,給客戶提供更好的服務。謝謝您的問題。

    提問:我想問田總一個問題,大家都關注奕澤的問題,我想問一下銷量的問題,2018年銷量目標定位69.5萬台,目標設定是基於什麼樣的考慮,今年將採取什麼樣的措施來確保目標的完成?

    田青久:我們考慮了幾個因素:

    第一,外部環境。媒體老師都知道,中國車市進入穩定低速增長狀態,前些年動輒二位數高速增長時代一去不復返。

    第二,內部因素。一汽豐田正處於發展調整期,今年有四款車型退市,奕澤下半年才上市量銷,有拉伸的過程。今年的增量,需要我們通過現有車型營銷努力,提高營銷質量來解決。

    那麼我們在制訂目標的時候,不局限於這個目標,而是要挑戰71,乃至於更高,具體涉及到打法、策略的問題。總體策略是12個字:“增量為本,節奏為先,結構為王”,找增量出口,這是本源。節奏上半年贏,全年贏,把車型銷售作為調整,有明星車型。具體的策略,用我的概來概括就是“非常6+1”,六個策略+一個特別關注的事情,具體分為品牌策略、車型策略、網絡策略、區域策略、經銷商盈利策略,一個就是跟經銷店溝通支持,提供快速解決。通過非常6+1的落實,Q1結果出來,銷售了16萬,加上關閉了的4款車型,去年同比增長11%,應該說順利實現開門紅。我相信後幾個季度堅定執行策略,很好的超額完成,在69.5萬基礎上的目標。

    提問:我想問水谷雅史先生一個問題,上午一汽豐田中國發布會公布了卡羅拉雙擎E+的上市會,你能否介紹一下這款車以及戰略意義?

    水谷雅史:感謝您的提問。今天上午剛剛公布了卡羅拉雙擎E+這款車型,我認為這款車型的戰略意義,未來面向新能源需求的1號車,具體會在2019年正式進行導入。

    我認為這款卡羅拉車型,是目前為止豐田在全球銷售的混合動力的進化版、升級版。大家可能都知道,豐田電動化車型在全世界銷售了100多個國家,銷售量達到1100萬台以上。那麼這一次,在原有車型基礎上,進一步技術能力出現了插電式混合動力卡羅拉雙擎E+的這款車型。這款車型未來面對中國能源政策,是一款毫無問題的車型,而且在充電續航能力上,即使沒有油電支持下,只有純電動的情況下,也可以行駛相當長距離。同時大家也知道,卡羅拉這款車型長期受到全球歡迎,結合上新能源技術,強強聯合出來的車型。

    我們會好好準備,把這樣的好產品奉獻給中國市場。這裏我再做一個預告,2020年作為新能源政策的對應第二號車型,是EV車型。再跟大家預告一下,這款車型將成為一汽豐田在全球導入第一款奕澤的EV車型,中國是先於全世界的任何一個國家。

    提問:移動互聯網增長迅猛,應對新時代變革方面,一汽豐田在数字營銷、粉絲營銷方面,有沒有什麼創新的想法?

    田青久:這個問題我來回答,一汽豐田董事長特別提倡營銷創新,有一句話創新到不能再創新。那麼一汽豐田的創新突破點在哪?最後選在数字體驗營銷方面。為什麼選擇這個端口呢?根據統計目前汽車市場消費者75%是80、90后,而這部分客戶從小接觸互聯網,是互聯網的原住民。在中國消費市場上,移動互聯網應用越來越廣泛,所以我們把創新的突破點鎖定在這個窗口上。

    具體正在嘗試做兩方面的工作,一是我們開發了数字聯合運營平台,流量聚合,全過程進行可視化管理,支持渠道(經銷商)DCC業務,提高信息量。第二方面打造粉絲營銷矩陣,600萬保有客戶、620家經銷家、3000零件供應商的現有龐大基盤、加上以在座各位為代表的四五百家友好的媒體、廣大的員工,這五個維度加在一起,有好內容產生的話,在粉絲營銷矩陣上一次觸達接近2000萬人群,二次裂變就可以覆蓋上億的人群。

    當今時代人人都是自媒體,就是媒體平台,傳播效率不言而喻。同時建立粉絲社群,通過跟粉絲互動,及時了解粉絲需求的效果,進而通過蓄水和放水的功能,實現銷售的轉化。假設按照0.5%轉化率來算,我們有200萬的粉絲,就是有一萬台增量。所以,在這方面积極探索。目前來看是效果是非常好的。今後大家有好主意,歡迎积極跟我說一下,吸收大家的智慧,我們一起來做嘗試。

    提問:我想問田總一個問題,去年600萬達成,一汽豐田推出“安享管家”計劃。能否從客戶利好的角度,給我們介紹具體的內容?

    田青久:營銷2.0時代汽車銷售與服務之外,確實各個品牌都在研究汽車延伸價值鏈的計劃。我們“安享計劃”,是把客戶在購車、用車過程當中的痛點都覆蓋到,具體安享計劃有八個方面,包括安心二手車、純牌零件、純正精品、安心租車、AAA延保、AAA保險、貼心金融等。只要用戶購車、用車的過程當中有困難,我們都有相應的產品服務來對應到他們。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 深入理解React:懶加載(lazy)實現原理

    目錄

    • 代碼分割
    • React的懶加載
      • import() 原理
      • React.lazy 原理
      • Suspense 原理
    • 參考

    1.代碼分割

    (1)為什麼要進行代碼分割?

    現在前端項目基本都採用打包技術,比如 Webpack,JS邏輯代碼打包後會產生一個 bundle.js 文件,而隨着我們引用的第三方庫越來越多或業務邏輯代碼越來越複雜,相應打包好的 bundle.js 文件體積就會越來越大,因為需要先請求加載資源之後,才會渲染頁面,這就會嚴重影響到頁面的首屏加載。

    而為了解決這樣的問題,避免大體積的代碼包,我們則可以通過技術手段對代碼包進行分割,能夠創建多個包並在運行時動態地加載。現在像 Webpack、 Browserify等打包器都支持代碼分割技術。

    (2)什麼時候應該考慮進行代碼分割?

    這裏舉一個平時開發中可能會遇到的場景,比如某個體積相對比較大的第三方庫或插件(比如JS版的PDF預覽庫)只在單頁應用(SPA)的某一個不是首頁的頁面使用了,這種情況就可以考慮代碼分割,增加首屏的加載速度。

    2.React的懶加載

    示例代碼:

    import React, { Suspense } from 'react';
    
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    
    function MyComponent() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <OtherComponent />
          </Suspense>
        </div>
      );
    }
    

    如上代碼中,通過 import() React.lazySuspense 共同一起實現了 React 的懶加載,也就是我們常說了運行時動態加載,即 OtherComponent 組件文件被拆分打包為一個新的包(bundle)文件,並且只會在 OtherComponent 組件渲染時,才會被下載到本地。

    那麼上述中的代碼拆分以及動態加載究竟是如何實現的呢?讓我們來一起探究其原理是怎樣的。

    import() 原理

    import() 函數是由TS39提出的一種動態加載模塊的規範實現,其返回是一個 promise。在瀏覽器宿主環境中一個import()的參考實現如下:

    function import(url) {
      return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
        script.type = "module";
        script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
    
        script.onload = () => {
          resolve(window[tempGlobal]);
          delete window[tempGlobal];
          script.remove();
        };
    
        script.onerror = () => {
          reject(new Error("Failed to load module script with URL " + url));
          delete window[tempGlobal];
          script.remove();
        };
    
        document.documentElement.appendChild(script);
      });
    }
    

    當 Webpack 解析到該import()語法時,會自動進行代碼分割。

    React.lazy 原理

    以下 React 源碼基於 16.8.0 版本

    React.lazy 的源碼實現如下:

    export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
      let lazyType = {
        $$typeof: REACT_LAZY_TYPE,
        _ctor: ctor,
        // React uses these fields to store the result.
        _status: -1,
        _result: null,
      };
    
      return lazyType;
    }
    

    可以看到其返回了一個 LazyComponent 對象。

    而對於 LazyComponent 對象的解析:

    ...
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    ...
    
    function mountLazyComponent(
      _current,
      workInProgress,
      elementType,
      updateExpirationTime,
      renderExpirationTime,
    ) { 
      ...
      let Component = readLazyComponentType(elementType);
      ...
    }
    
    // Pending = 0, Resolved = 1, Rejected = 2
    export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
      const status = lazyComponent._status;
      const result = lazyComponent._result;
      switch (status) {
        case Resolved: {
          const Component: T = result;
          return Component;
        }
        case Rejected: {
          const error: mixed = result;
          throw error;
        }
        case Pending: {
          const thenable: Thenable<T, mixed> = result;
          throw thenable;
        }
        default: { // lazyComponent 首次被渲染
          lazyComponent._status = Pending;
          const ctor = lazyComponent._ctor;
          const thenable = ctor();
          thenable.then(
            moduleObject => {
              if (lazyComponent._status === Pending) {
                const defaultExport = moduleObject.default;
                lazyComponent._status = Resolved;
                lazyComponent._result = defaultExport;
              }
            },
            error => {
              if (lazyComponent._status === Pending) {
                lazyComponent._status = Rejected;
                lazyComponent._result = error;
              }
            },
          );
          // Handle synchronous thenables.
          switch (lazyComponent._status) {
            case Resolved:
              return lazyComponent._result;
            case Rejected:
              throw lazyComponent._result;
          }
          lazyComponent._result = thenable;
          throw thenable;
        }
      }
    }
    

    注:如果 readLazyComponentType 函數多次處理同一個 lazyComponent,則可能進入Pending、Rejected等 case 中。

    從上述代碼中可以看出,對於最初 React.lazy() 所返回的 LazyComponent 對象,其 _status 默認是 -1,所以首次渲染時,會進入 readLazyComponentType 函數中的 default 的邏輯,這裏才會真正異步執行 import(url)操作,由於並未等待,隨後會檢查模塊是否 Resolved,如果已經Resolved了(已經加載完畢)則直接返回moduleObject.default(動態加載的模塊的默認導出),否則將通過 throw 將 thenable 拋出到上層。

    為什麼要 throw 它?這就要涉及到 Suspense 的工作原理,我們接着往下分析。

    Suspense 原理

    由於 React 捕獲異常並處理的代碼邏輯比較多,這裏就不貼源碼,感興趣可以去看 throwException 中的邏輯,其中就包含了如何處理捕獲的異常。簡單描述一下處理過程,React 捕獲到異常之後,會判斷異常是不是一個 thenable,如果是則會找到 SuspenseComponent ,如果 thenable 處於 pending 狀態,則會將其 children 都渲染成 fallback 的值,一旦 thenable 被 resolve 則 SuspenseComponent 的子組件會重新渲染一次。

    為了便於理解,我們也可以用 componentDidCatch 實現一個自己的 Suspense 組件,如下:

    class Suspense extends React.Component {
      state = {
        promise: null
      }
    
      componentDidCatch(err) {
        // 判斷 err 是否是 thenable
        if (err !== null && typeof err === 'object' && typeof err.then === 'function') {
          this.setState({ promise: err }, () => {
            err.then(() => {
              this.setState({
                promise: null
              })
            })
          })
        }
      }
    
      render() {
        const { fallback, children } = this.props
        const { promise } = this.state
        return <>{ promise ? fallback : children }</>
      }
    }
    

    小結

    至此,我們分析完了 React 的懶加載原理。簡單來說,React利用 React.lazyimport()實現了渲染時的動態加載 ,並利用Suspense來處理異步加載資源時頁面應該如何显示的問題。

    3.參考

    代碼分割– React

    動態import – MDN – Mozilla

    proposal-dynamic-import

    React Lazy 的實現原理

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 用 Explain 命令分析 MySQL 的 SQL 執行

    用 Explain 命令分析 MySQL 的 SQL 執行

    在上一篇文章《MySQL常見加鎖場景分析》中,我們聊到行鎖是加在索引上的,但是複雜的 SQL 往往包含多個條件,涉及多個索引,找出 SQL 執行時使用了哪些索引對分析加鎖場景至關重要。

    比如下面這樣的 SQL:

    mysql> delete from t1 where id = 1 or val = 1
    

    其中 id 和 val 都是索引,那麼執行時使用到了哪些索引,加了哪些鎖呢?為此,我們需要使用 explain 來獲取 MySQL 執行這條 SQL 的執行計劃。

    什麼是執行計劃呢?簡單來說,就是 SQL 在數據庫中執行時的表現情況,通常用於 SQL 性能分析、優化和加鎖分析等場景,執行過程會在 MySQL 查詢過程中由解析器,預處理器和查詢優化器共同生成。

    MySQL 查詢過程

    如果能搞清楚 MySQL 是如何優化和執行查詢的,不僅對優化查詢一定會有幫助,還可以通過分析使用到的索引來判斷最終的加鎖場景。

    下圖是MySQL執行一個查詢的過程。實際上每一步都比想象中的複雜,尤其優化器,更複雜也更難理解。本文只給予簡單的介紹。

    MySQL查詢過程如下:

    • 客戶端發送一條查詢給服務器。
    • 服務器先檢查查詢緩存,如果命中了緩存,則立刻返回存儲在緩存中的結果。否則進入下一階段。
    • 服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃。
    • MySQL根據優化器生成的執行計劃,再調用存儲引擎的API來執行查詢。
    • 將結果返回給客戶端。

    執行計劃

    MySQL會解析查詢,並創建內部數據結構(解析樹),並對其進行各種優化,包括重寫查詢、決定表的讀取順序、選擇合適的索引等。

    用戶可通過關鍵字提示(hint)優化器,從而影響優化器的決策過程。也可以通過 explain 了解 數據庫是如何進行優化決策的,並提供一個參考基準,便於用戶重構查詢和數據庫表的 schema、修改數據庫配置等,使查詢盡可能高效。

    下面,我們依次介紹 explain 中相關輸出參數,並以實際例子解釋這些參數的含義。

    select_type

    查詢數據的操作類型,有如下

    • simple 簡單查詢,不包含子查詢或 union,如下圖所示,就是最簡單的查詢語句。
    • primary 是 SQL 中包含複雜的子查詢,此時最外層查詢標記為該值。

    • derived 是 SQL 中 from 子句中包含的子查詢被標記為該值,MySQL 會遞歸執行這些子查詢,把結果放在臨時表。下圖展示了上述兩種類型。

    • subquery 是 SQL 在 select 或者 where 里包含的子查詢,被標記為該值。
    • dependent subquery:子查詢中的第一個 select,取決於外側的查詢,一般是 in 中的子查詢。
    • union 是 SQL 在出現在 union 關鍵字之後的第二個 select ,被標記為該值;若 union 包含在 from 的子查詢中,外層select 被標記為 derived。

    • union result 從 union 表獲取結果的 select。下圖展示了 union 和 union result 的 SQL 案例。

    • dependent union 也是 union 關鍵字之後的第二個或者後邊的那個 select 語句,和 dependent subquery 一樣,取決於外面的查詢。

    type

    表的連接類型,其性能由高到低排列為 system,const,eq_ref,ref,range,index 和 all。

    • system 表示表只有一行記錄,相當於系統表。如下圖所示,因為 from 的子查詢派生的表只有一行數據,所以 primary 的表連接類型為 system。
    • const 通過索引一次就找到,只匹配一行數據,用於常數值比較PRIMARY KEY 或者 UNIQUE索引。
    • eq_ref 唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常用於主鍵或唯一索引掃描。對於每個來自前邊的表的行組合,從該表中讀取一行。它是除了 const 類型外最好的連接類型。

      如下圖所示,對錶 t1 查詢的 type 是 ALL,表示全表掃描,然後 t1 中每一行數據都來跟 t2.id 這個主鍵索引進行對比,所以 t2 表的查詢就是 eq_ref。

    • ref 非唯一性索引掃描,返回匹配某個單獨值的所有行,和 eq_ref 的區別是索引是非唯一索引,具體案例如下所示。
    • range 只檢查給定範圍的行,使用一個索引來選擇行,當使用 =, between, >, <, 和 in 等操作符,並使用常數比較關鍵列時。如下圖所示,其中 id 為唯一索引,而 val 是非唯一索引。
    • index 與 ALL 類型類似,唯一區別就是只遍歷索引樹讀取索引值,比 ALL 讀取所有數據行要稍微快一些,因為索引文件通常比數據文件小。這裏涉及 MySQL 的索引覆蓋

    • ALL 全表掃描,通常情況下性能很差,應該避免。

    possible_keys,key 和 key_len

    possible_key 列指出 MySQL 可能使用哪個索引在該表中查找。如果該列為 NULL,則沒有使用相關索引。需要檢查 where 子句條件來創建合適的索引提高查詢效率。

    key 列显示 MySQL 實際決定使用的索引。如果沒有選擇索引,則值為 NULL。

    key_len 显示 MySQL 決定使用索引的長度。如果鍵為 NULL,則本列也為 NULL,使用的索引長度,在保證精確度的情況下,越短越好。因為越短,索引文件越小,需要的 I/O次數也越少。

    由上圖可以看出,對於 select * from t2 where id = 1 or val = 1這個語句,可以使用 PRIMARY 或者 idx_t2_val 索引,實際使用了 idx_t2_val 索引,索引的長度為5。

    這些其實是我們分析加鎖場景最為關心的字段,後續文章會具體講解如何根據這些字段和其他工具一起判斷複雜 SQL 到底加了哪些鎖。

    ref

    ref 列表示使用其他表的哪個列或者常數來從表中選擇行。如下圖所示,從 t2 讀取數據時,要判斷 t2.id = t1.id,所以 ref 就是 mysql.t1.id

    rows 和 filtered

    rows 列显示 MySQL 認為它執行查詢時必須檢查的行數。

    filtered 列表明了 SQL 語句執行后返回結果的行數占讀取行數的百分比,值越大越好。MySQL 會使用 Table Filter 來讀取出來的行數據進行過濾,理論上,讀取出來的行等於返回結果的行數時效率最高,過濾的比率越多,效率越低。

    如上圖所示,t1表中有三條數據,rows 為 3,表示所有行都要讀取出來。根據 val = 3 這個 table filter 過濾,只返回一行數據,所以 filtered 比例為33.33%,

    extra

    包含不適合在其他列中显示但十分重要的額外信息。常見的值如下

    • using index 表示 select 操作使用了覆蓋索引,避免了訪問表的數據行,效率不錯。

    • using where 子句用於限制哪一行。也就是讀取數據后使用了 Table Filter 進行過濾。

      如下圖所示,因為 id 和 val 都是有索引的,所以 select * 也是可以直接使用覆蓋索引讀取數據,所以 extra 中有 using index。而因為只使用 val 索引讀取了3行數據,還是通過 where 子句進行過濾,filtered為 55%,所以 extra 中使用了 using where。

    • using filesort MySQL 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,若出現該值,應該優化 SQL 語句。如下圖所示,其中 val 列沒有索引,所以無法使用索引順序排序讀取。
    • using temporary 使用臨時表保存中間結果,比如,MySQL 在對查詢結果排序時使用臨時表,常用於 order by 和 group by,如果出現該值,應該優化 SQL。根據我的經驗,group by 一個無索引列,或者ORDER BY 或 GROUP BY 的列不是來自JOIN語句序列的第一個表,就會產生臨時表。

    • using join buffer 使用連接緩存。如下圖所示,展示了連接緩存和臨時表。關於連接緩存的內容,大家可以自行查閱,後續有時間在寫文章解釋。

    • distinct 發現第一個匹配后,停止為當前的行組合搜索更多的行

    後記

    通過 explain 了解到 SQL 的執行計劃后,我們不僅可以了解 SQL 執行時使用的索引,判斷加鎖場景,還可以針對其他信息對 SQL 進行優化分析,比如將 type 類型從 index 優化到 ref 等。

    個人博客,歡迎來玩

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 漢蘭達提車等幾個月,不如看看這款2.0T+9AT的7座SUV

    漢蘭達提車等幾個月,不如看看這款2.0T+9AT的7座SUV

    0T+愛信6AT雖然動力數據不及大指揮官,但勝在穩定可靠。豐田在漢蘭達的動力調校上要比Jeep更為得心應手,低轉下的扭矩較為充沛,油門調校也有一貫日系車的風格,初段頗為靈敏。在零百成績方面,漢蘭達為9。13秒。儘管如此,在高速上再加速時,漢蘭達依舊顯得底氣十足。

    如果要買一輛7座中型SUV來作為家庭出行的首選,我想很多人都會第一時間想到漢蘭達。畢竟這麼多年的口碑擺在那裡,各方面的實力都較為均衡。奈何漢蘭達的產能有限,導致不少地區要等好幾個月,甚至加價才能提車。

    幾年前,福特推出銳界試圖撼動漢蘭達的地位,結果也只是成了不少人買不到漢蘭達,退而求其次的選擇。這回Jeep推出大指揮官也是試圖重新攪動這個市場,好好地打擊一下漢蘭達,那究竟指揮官有沒有這樣的實力呢?虎哥就為大家好好分析一下。

    從動力參數來看,大指揮官無論是最大馬力還是最大扭矩都比漢蘭達多不少,而且在整備質量方面,大指揮官比漢蘭達還要輕75kg,相當於一個成年男性的質量。不過,參數只是一個方面,更重要的還是動力匹配。

    大指揮官上的這副2.0T發動機與Giulia上的是同根同源,與之匹配的是ZF的9AT變速箱。日常駕駛時,能發現這副9AT還是偏向於平順的調校,低速蠕行時沒有什麼頓挫。

    這樣的調校會導致大指揮官在急加速時的換擋偏慢,好在這副2.0T的機器底氣較足,很多時候並不需要降太多的擋位便可以完成加速。大指揮官實測的零百成績能少於8秒,比漢蘭達要快不少。大指揮官在走一些顛簸路時,濾震不錯,有一定的厚實感,只是偏軟的彈簧導致剎車時容易點頭。

    漢蘭達的2.0T+愛信6AT雖然動力數據不及大指揮官,但勝在穩定可靠。豐田在漢蘭達的動力調校上要比Jeep更為得心應手,低轉下的扭矩較為充沛,油門調校也有一貫日系車的風格,初段頗為靈敏。

    在零百成績方面,漢蘭達為9.13秒。儘管如此,在高速上再加速時,漢蘭達依舊顯得底氣十足。底盤調校上,漢蘭達偏重於舒適,懸挂的工作也較為無感。走爛路時,漢蘭達的濾震厚實,無論是駕駛員還是乘客都坐得較為舒適。

    大指揮官的二三排

    從上面的對比表格不難看出,漢蘭達更傾向於做好第二排,所以第二排在最小時也不至於頂到前排。同時,全平的中央地板也為其加分不少。但是在第三排方面,明顯就不如大指揮官。

    漢蘭達的二三排

    雖然大指揮官的二排在最小和最大腿部空間上都不如漢蘭達,但是第三排在最好的情況下能調出四指的腿部空間。不過這還是有點雞肋,滿載時,不僅三排的人會坐得比較局促,連二排乘客也會隨之遭殃。

    從上面的配置對比來看,在相同指導價下,大指揮官的配置表現是要略微好於漢蘭達。儘管這款漢蘭達的售價高達30多萬,但依舊採用的是鹵素大燈,車窗一鍵升降也僅裝備於前排,這些都是不應該的。

    總結

    大指揮官的表現雖然不錯,但在這個級別消費者最需要的舒適和大空間方面,還是與漢蘭達拉不開距離。倒是在動力和配置方面,能展現出自己的優勢,這樣的表現與銳界有幾分相似。對於消費者而言,多一個選擇總歸還是一件好事情,但恐怕大指揮官還是無法撼動漢蘭達的地位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

    ※台北網頁設計公司全省服務真心推薦

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

    ※推薦評價好的iphone維修中心

  • 用python做時間序列預測九:ARIMA模型簡介

    用python做時間序列預測九:ARIMA模型簡介

    本篇介紹時間序列預測常用的ARIMA模型,通過了解本篇內容,將可以使用ARIMA預測一個時間序列。

    什麼是ARIMA?

    • ARIMA是’Auto Regressive Integrated Moving Average’的簡稱。
    • ARIMA是一種基於時間序列歷史值和歷史值上的預測誤差來對當前做預測的模型。
    • ARIMA整合了自回歸項AR和滑動平均項MA。
    • ARIMA可以建模任何存在一定規律的非季節性時間序列。
    • 如果時間序列具有季節性,則需要使用SARIMA(Seasonal ARIMA)建模,後續會介紹。

    ARIMA模型參數

    ARIMA模型有三個超參數:p,d,q

    • p
      AR(自回歸)項的階數。需要事先設定好,表示y的當前值和前p個歷史值有關。
    • d
      使序列平穩的最小差分階數,一般是1階。非平穩序列可以通過差分來得到平穩序列,但是過度的差分,會導致時間序列失去自相關性,從而失去使用AR項的條件。
    • q
      MA(滑動平均)項的階數。需要事先設定好,表示y的當前值和前q個歷史值AR預測誤差有關。實際是用歷史值上的AR項預測誤差來建立一個類似歸回的模型。

    ARIMA模型表示

    • AR項表示
      一個p階的自回歸模型可以表示如下:

      c是常數項,εt是隨機誤差項。
      對於一個AR(1)模型而言:
      當 ϕ1=0 時,yt 相當於白噪聲;
      當 ϕ1=1 並且 c=0 時,yt 相當於隨機遊走模型;
      當 ϕ1=1 並且 c≠0 時,yt 相當於帶漂移的隨機遊走模型;
      當 ϕ1<0 時,yt 傾向於在正負值之間上下浮動。

    • MA項表示
      一個q階的預測誤差回歸模型可以表示如下:

      c是常數項,εt是隨機誤差項。
      yt 可以看成是歷史預測誤差的加權移動平均值,q指定了歷史預測誤差的期數。

    • 完整表示

      即: 被預測變量Yt = 常數+Y的p階滯后的線性組合 + 預測誤差的q階滯后的線性組合

    ARIMA模型定階

    看圖定階

    差分階數d
    • 如果時間序列本身就是平穩的,就不需要差分,所以此時d=0。
    • 如果時間序列不平穩,那麼主要是看時間序列的acf圖,如果acf表現為10階或以上的拖尾,那麼需要進一步的差分,如果acf表現為1階截尾,則可能是過度差分了,最好的差分階數是使acf先拖尾幾階,然後截尾。
    • 有的時候,可能在2個階數之間無法確定用哪個,因為acf的表現差不多,那麼就選擇標準差小的序列。
    • 下面是原時間序列、一階差分后、二階差分后的acf圖:

      可以看到,原序列的acf圖的拖尾階數過高了,而二階差分后的截尾階數過小了,所以一階差分更合適。

    python代碼:

    import numpy as np, pandas as pd
    from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
    import matplotlib.pyplot as plt
    plt.rcParams.update({'figure.figsize':(9,7), 'figure.dpi':120})
    
    # Import data : Internet Usage per Minute
    df = pd.read_csv('https://raw.githubusercontent.com/selva86/datasets/master/wwwusage.csv', names=['value'], header=0)
    
    # Original Series
    fig, axes = plt.subplots(3, 2, sharex=True)
    axes[0, 0].plot(df.value); axes[0, 0].set_title('Original Series')
    plot_acf(df.value, ax=axes[0, 1])
    
    # 1st Differencing
    axes[1, 0].plot(df.value.diff()); axes[1, 0].set_title('1st Order Differencing')
    plot_acf(df.value.diff().dropna(), ax=axes[1, 1])
    
    # 2nd Differencing
    axes[2, 0].plot(df.value.diff().diff()); axes[2, 0].set_title('2nd Order Differencing')
    plot_acf(df.value.diff().diff().dropna(), ax=axes[2, 1])
    
    plt.show()
    
    AR階數p

    AR的階數p可以通過pacf圖來設定,因為AR各項的係數就代表了各項自變量x對因變量y的偏自相關性。

    可以看到,lag1,lag2之後,偏自相關落入了藍色背景區間內,表示不相關,所以這裏階數可以選擇2,或者保守點選擇1。

    MA階數q

    MA階數通過acf圖來設定,因為MA是預測誤差,預測誤差是自回歸預測和真實值之間的偏差。定階過程類似AR階數的設定過程。這裏可以選擇3,或者保守點選擇2。

    信息準則定階

    • AIC(Akaike Information Criterion)

      L是數據的似然函數,k=1表示模型考慮常數c,k=0表示不考慮。最後一個1表示算上誤差項,所以其實第二項就是2乘以參數個數。

    • AICc(修正過的AIC)
    • BIC(Bayesian Information Criterion)

    注意事項:

    • 信息準則越小,說明參數的選擇越好,一般使用AICc或者BIC。
    • 差分d,不要使用信息準則來判斷,因為差分會改變了似然函數使用的數據,使得信息準則的比較失去意義,所以通常用別的方法先選擇出合適的d。
    • 信息準則的好處是可以在用模型給出預測之前,就對模型的超參做一個量化評估,這對批量預測的場景尤其有用,因為批量預測往往需要在程序執行過程中自動定階。

    構建ARIMA模型

    from statsmodels.tsa.arima_model import ARIMA
    
    # 1,1,2 ARIMA Model
    model = ARIMA(df.value, order=(1,1,2))
    model_fit = model.fit(disp=0)
    print(model_fit.summary())
    

    中間的表格列出了訓練得到的模型各項和對應的係數,如果係數很小,且‘P>|z|’ 列下的P-Value值遠大於0.05,則該項應該去掉,比如上圖中的ma部分的第二項,係數是-0.0010,P-Value值是0.998,那麼可以重建模型為ARIMA(1,1,1),從下圖可以看到,修改階數后的模型的AIC等信息準則都有所降低:

    檢查殘差

    通常會檢查模型擬合的殘差序列,即訓練數據原本的序列減去訓練數據上的擬合序列后的序列。該序列越符合隨機誤差分佈(均值為0的正態分佈),說明模型擬合的越好,否則,說明還有一些因素模型未能考慮。

    • python實現:
    # Plot residual errors
    residuals = pd.DataFrame(model_fit.resid)
    fig, ax = plt.subplots(1,2)
    residuals.plot(title="Residuals", ax=ax[0])
    residuals.plot(kind='kde', title='Density', ax=ax[1])
    plt.show()
    
    

    模型擬合

    # Actual vs Fitted
    model_fit.plot_predict(dynamic=False)
    plt.show()
    

    模型預測

    除了在訓練數據上擬合,一般都會預留一部分時間段作為模型的驗證,這部分時間段的數據不參与模型的訓練。

    from statsmodels.tsa.stattools import acf
    
    # Create Training and Test
    train = df.value[:85]
    test = df.value[85:]
    
    # Build Model
    # model = ARIMA(train, order=(3,2,1))  
    model = ARIMA(train, order=(1, 1, 1))  
    fitted = model.fit(disp=-1)  
    
    # Forecast
    fc, se, conf = fitted.forecast(15, alpha=0.05)  # 95% conf
    
    # Make as pandas series
    fc_series = pd.Series(fc, index=test.index)
    lower_series = pd.Series(conf[:, 0], index=test.index)
    upper_series = pd.Series(conf[:, 1], index=test.index)
    
    # Plot
    plt.figure(figsize=(12,5), dpi=100)
    plt.plot(train, label='training')
    plt.plot(test, label='actual')
    plt.plot(fc_series, label='forecast')
    plt.fill_between(lower_series.index, lower_series, upper_series, 
                     color='k', alpha=.15)
    plt.title('Forecast vs Actuals')
    plt.legend(loc='upper left', fontsize=8)
    plt.show()
    

    這是在ARIMA(1,1,1)下的預測結果,給出了一定的序列變化方向,看上去還是可以的。不過所有的預測值,都在真實值以下,所以還可以試試看有沒有別的更好的階數組合。
    其實如果嘗試用ARIMA(3,2,1)會發現預測的更好:

    AUTO ARIMA

    通過預測結果來推斷模型階數的好壞畢竟還是耗時耗力了些,一般可以通過計算AIC或BIC的方式來找出更好的階數組合。pmdarima模塊的auto_arima方法就可以讓我們指定一個階數上限和信息準則計算方法,從而找到信息準則最小的階數組合。

    from statsmodels.tsa.arima_model import ARIMA
    import pmdarima as pm
    
    df = pd.read_csv('https://raw.githubusercontent.com/selva86/datasets/master/wwwusage.csv', names=['value'], header=0)
    
    model = pm.auto_arima(df.value, start_p=1, start_q=1,
                          information_criterion='aic',
                          test='adf',       # use adftest to find optimal 'd'
                          max_p=3, max_q=3, # maximum p and q
                          m=1,              # frequency of series
                          d=None,           # let model determine 'd'
                          seasonal=False,   # No Seasonality
                          start_P=0, 
                          D=0, 
                          trace=True,
                          error_action='ignore',  
                          suppress_warnings=True, 
                          stepwise=True)
    
    print(model.summary())
    
    # Forecast
    n_periods = 24
    fc, confint = model.predict(n_periods=n_periods, return_conf_int=True)
    index_of_fc = np.arange(len(df.value), len(df.value)+n_periods)
    
    # make series for plotting purpose
    fc_series = pd.Series(fc, index=index_of_fc)
    lower_series = pd.Series(confint[:, 0], index=index_of_fc)
    upper_series = pd.Series(confint[:, 1], index=index_of_fc)
    
    # Plot
    plt.plot(df.value)
    plt.plot(fc_series, color='darkgreen')
    plt.fill_between(lower_series.index, 
                     lower_series, 
                     upper_series, 
                     color='k', alpha=.15)
    
    plt.title("Final Forecast of WWW Usage")
    plt.show()
    

    從輸出可以看到,模型採用了ARIMA(3,2,1)的組合來預測,因為該組合計算出的AIC最小。

    如何自動構建季節性ARIMA模型?

    如果模型帶有季節性,則除了p,d,q以外,模型還需要引入季節性部分:

    與非季節性模型的區別在於,季節性模型都是以m為固定周期來做計算的,比如D就是季節性差分,是用當前值減去上一個季節周期的值,P和Q和非季節性的p,q的區別也是在於前者是以季節窗口為單位,而後者是連續時間的。
    上節介紹的auto arima的代碼中,seasonal參數設為了false,構建季節性模型的時候,把該參數置為True,然後對應的P,D,Q,m參數即可,代碼如下:

    # !pip3 install pyramid-arima
    import pmdarima as pm
    # Seasonal - fit stepwise auto-ARIMA
    smodel = pm.auto_arima(data, start_p=1, start_q=1,
                             test='adf',
                             max_p=3, max_q=3, m=12,
                             start_P=0, seasonal=True,
                             d=None, D=1, trace=True,
                             error_action='ignore',  
                             suppress_warnings=True, 
                             stepwise=True)
    smodel.summary()
    

    注意這裏的stepwise參數,默認值就是True,表示用stepwise algorithm來選擇最佳的參數組合,會比計算所有的參數組合要快很多,而且幾乎不會過擬合,當然也有可能忽略了最優的組合參數。所以如果你想讓模型自動計算所有的參數組合,然後選擇最優的,可以將stepwise設為False。

    如何在預測中引入其它相關的變量?

    在時間序列模型中,還可以引入其它相關的變量,這些變量稱為exogenous variable(外生變量,或自變量),比如對於季節性的預測,除了之前說的通過加入季節性參數組合以外,還可以通過ARIMA模型加外生變量來實現,那麼這裏要加的外生變量自然就是時間序列中的季節性序列了(通過時間序列分解得到)。需要注意的是,對於季節性來說,還是用季節性模型來擬合比較合適,這裏用外生變量的方式只是為了方便演示外生變量的用法。因為對於引入了外生變量的時間序列模型來說,在預測未來的值的時候,也要對外生變量進行預測的,而用季節性做外生變量的方便演示之處在於,季節性每期都一樣的,比如年季節性,所以直接複製到3年就可以作為未來3年的季節外生變量序列了。

    
    def load_data():
        """
        航司乘客數時間序列數據集
        該數據集包含了1949-1960年每個月國際航班的乘客總數。
        """
        from datetime import datetime
        date_parse = lambda x: datetime.strptime(x, '%Y-%m-%d')
        data = pd.read_csv('https://www.analyticsvidhya.com/wp-content/uploads/2016/02/AirPassengers.csv', index_col='Month', parse_dates=['Month'], date_parser=date_parse)
        # print(data)
        # print(data.index)
        ts = data['value']
        # print(ts.head(10))
        # plt.plot(ts)
        # plt.show()
        return ts,data
    
    # 加載時間序列數據
    _ts,_data = load_data()
    # 時間序列分解
    result_mul = seasonal_decompose(_ts[-36:],  # 3 years
                                    model='multiplicative',
                                    freq=12,
                                    extrapolate_trend='freq')
    _seasonal_frame = result_mul.seasonal[-12:].to_frame()
    _seasonal_frame['month'] = pd.to_datetime(_seasonal_frame.index).month
    # seasonal_index = result_mul.seasonal[-12:].index
    # seasonal_index['month'] = seasonal_index.month.values
    print(_seasonal_frame)
    _data['month'] = _data.index.month
    print(_data)
    _df = pd.merge(_data, _seasonal_frame, how='left', on='month')
    _df.columns = ['value', 'month', 'seasonal_index']
    print(_df)
    print(_df.index)
    _df.index = _data.index  # reassign the index.
    print(_df.index)
    
    build_arima(_df,_seasonal_frame,_data)
    
    # SARIMAX Model
    sxmodel = pm.auto_arima(df[['value']],
    						exogenous=df[['seasonal_index']],
    						start_p=1, start_q=1,
    						test='adf',
    						max_p=3, max_q=3, m=12,
    						start_P=0, seasonal=False,
    						d=1, D=1, trace=True,
    						error_action='ignore',
    						suppress_warnings=True,
    						stepwise=True)
    sxmodel.summary()
    # Forecast
    n_periods = 36
    fitted, confint = sxmodel.predict(n_periods=n_periods,
    								  exogenous=np.tile(seasonal_frame['y'].values, 3).reshape(-1, 1),
    								  return_conf_int=True)
    index_of_fc = pd.date_range(data.index[-1], periods = n_periods, freq='MS')
    # make series for plotting purpose
    fitted_series = pd.Series(fitted, index=index_of_fc)
    lower_series = pd.Series(confint[:, 0], index=index_of_fc)
    upper_series = pd.Series(confint[:, 1], index=index_of_fc)
    
    # Plot
    plt.plot(data['y'])
    plt.plot(fitted_series, color='darkgreen')
    plt.fill_between(lower_series.index,
    				 lower_series,
    				 upper_series,
    				 color='k', alpha=.15)
    
    plt.title("SARIMAX Forecast of a10 - Drug Sales")
    plt.show()
    
    

    以下是結果比較:

    • 選擇ARIMA(3,1,1)來預測:
    • 選擇季節性模型SARIMA(3,0,1),(0,1,0,12)來預測:
    • 選擇帶季節性外生變量的ARIMA(3,1,1)來預測:

    ok,本篇就這麼多內容啦~,下一篇將基於一個實際的例子來介紹完整的預測實現過程,感謝閱讀O(∩_∩)O。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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