超強教程!在樹莓派上構建多節點K8S集群!

在很長一段時間里,我對於在樹莓派上搭建Kubernetes集群極為感興趣。在網絡上找到一些教程並且跟着實操,我已經能夠將Kubernetes安裝在樹莓派上,並在三個Pi集群中工作。然而,在master節點上對於RAM和CPU的要求已經超過了我的樹莓派所能提供的,因此在執行Kubernetes任務時性能並不優異。這也使得就地升級Kubernetes成為不可能。

所以,我看到業界應用最為廣泛的Kubernetes管理平台創建者Rancher Labs推出輕量級Kubernetes發行版K3s時,十分興奮,它專為資源受限場景而設計,還針對ARM處理器進行了優化,這使得在樹莓派上運行Kubernetes更加可行。在本文中,我將使用K3s和樹莓派創建一個Kubernetes集群。

前期準備

要創建本文中的Kubernetes集群,我們需要準備:

  • 至少一個樹莓派(帶有SD卡和電源適配器)

  • 以太網電纜

  • 將我們所有的樹莓派連接在一起的交換機或路由器

我將從網絡上安裝K3s,所以需要通過路由器訪問互聯網。

集群架構

對於這一集群,我們將使用3個樹莓派。第一個樹莓派我把它命名為kmaster,並分配一個靜態IP 192.168.0.50(因為我的本地網絡是192.168.0.0/24)。第一個worker節點(也就是第二個Pi),我們稱它為knode1並分配IP 192.168.0.51。最後一個worker節點,我們稱它為knode2並分配IP 192.168.0.52。

當然如果你的網絡和我不一樣,可以使用你能夠獲得網絡IP。只要在本文使用IP的任何地方替換你自己的值即可。

為了不必再通過IP引用每個節點,我們將其主機名添加到PC上的/ etc / hosts文件中。

echo -e "192.168.0.50\tkmaster" | sudo tee -a /etc/hosts
echo -e "192.168.0.51\tknode1" | sudo tee -a /etc/hosts
echo -e "192.168.0.52\tknode2" | sudo tee -a /etc/hosts

安裝master節點

現在我們已經準備好,可以開始安裝master節點。第一步,安裝最新的Raspbian鏡像。我之前寫過一篇詳細的文章介紹為什麼需要最新的鏡像,感興趣的朋友可以在訪問鏈接查看:

https://carpie.net/articles/headless-pi-with-static-ip-wired-edition

接下來,開始安裝Raspbian,啟用SSH server,為kmaster設置主機名稱並分配靜態IP 192.168.0.50。

既然Raspbian已經在master節點上安裝完畢,讓我們啟用我們的master Pi並通過ssh進入它:

ssh pi@kmaster

現在我們要準備安裝K3s。在master Pi上運行:

curl -sfL https://get.k3s.io | sh -

命令執行完畢之後,我們就有了一個已經設置好的單節點集群並且正在運行中!讓我們檢查一下。依舊是在這個Pi上,運行:

sudo kubectl get nodes

你應該看到類似以下內容:

NAME     STATUS   ROLES    AGE    VERSION
kmaster  Ready    master   2m13s  v1.14.3-k3s.1

提取join token

我們想要添加一對worker節點。在這些節點上安裝K3s,我們需要一個join token。Join token存在於master節點的文件系統上。讓我們複製並將它保存在某個地方,稍後我們可以獲取它:

sudo cat /var/lib/rancher/k3s/server/node-token

安裝worker節點

為兩個worker節點獲取一些SD卡,並在每個節點上安裝Raspbian。對於其中一個,將主機名設置為knode1並分配IP 192.168.0.51。對於另一個,將主機名設置為knode2並分配IP 192.168.0.52。現在,讓我們安裝K3s。

啟動你的第一個worker節點,並通過ssh進入它:

ssh pi@knode1

在這個Pi上,我們將像之前一樣安裝K3s,但我們將給安裝程序額外的參數,讓它了解我們正在安裝一個worker節點並且要加入一個現有集群:

curl -sfL http://get.k3s.io | K3S_URL=https://192.168.0.50:6443 \
K3S_TOKEN=join_token_we_copied_earlier sh -

使用從上個部分提取出來的join token替換join_token_we_copied_earlier。為knode2重複這些步驟。

從我們的PC訪問集群

每當我們要檢查或修改集群時,都必須通過SSH到master節點來運行kubectl,這很煩人。因此,我們像將kubectl放在我們的PC上,但是首先讓我們從master節點獲取所需的配置信息。通過SSH進入kmaster,並運行:

sudo cat /etc/rancher/k3s/k3s.yaml

複製配置信息並返回到你的PC。為配置創建一個目錄:

mkdir ~/.kube

保存複製的配置為~/.kube/config。現在編輯文件並更改:

server: https://localhost:6443

改為:

server: https://kmaster:6443

為了安全起見,請將文件的讀/寫權限限製為你自己:

chmod 600 ~/.kube/config

現在讓我們在我們的PC上安裝kubectl(如果你還沒有)。Kubernetes網站上有針對各種平台執行此操作的說明。由於我正在運行Linux Mint(一個Ubuntu衍生版本),因此我將在此處显示Ubuntu的說明:

sudo apt update && sudo apt install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | \
sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt update && sudo apt install kubectl

如果你還不熟悉,上述命令為Kubernetes添加了一個Debian倉庫,獲取其GPG密鑰以確保安全,然後更新軟件包列表並安裝kubectl。現在,我將通過標準軟件更新機制獲得有關kubectl更新的通知。

現在我們可以從我們的PC檢查我們的集群,運行:

kubectl get nodes

你應該看到類似以下內容:

NAME     STATUS  ROLES   AGE   VERSION
kmaster  Ready   master  12m   v1.14.3-k3s.1
knode1   Ready   worker  103s  v1.14.3-k3s.1
knode1   Ready   worker  103s  v1.14.3-k3s.1

Congratulations!你現在已經有一個正在工作的3個節點的Kubernetes集群!

使用K3s的附加 bonus

如果你運行kubectl get pods –all-namespaces,你將看到一些Traefik的額外pod。Treafik是一個反向代理和負載均衡器,我們可以使用它從單個入口點將流量引導到我們的集群中。Kubernetes當然也可以安裝Traefik,但不是默認提供的。所以K3s中默認提供Traefik是一個非常棒的設計!

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

Java規則引擎 Easy Rules

1.  Easy Rules 概述

Easy Rules是一個Java規則引擎,靈感來自一篇名為《Should I use a Rules Engine?》的文章 

規則引擎就是提供一種可選的計算模型。與通常的命令式模型(由帶有條件和循環的命令依次組成)不同,規則引擎基於生產規則系統。這是一組生產規則,每條規則都有一個條件(condition)和一個動作(action)———— 簡單地說,可以將其看作是一組if-then語句。

精妙之處在於規則可以按任何順序編寫,引擎會決定何時使用對順序有意義的任何方式來計算它們。考慮它的一個好方法是系統運行所有規則,選擇條件成立的規則,然後執行相應的操作。這樣做的好處是,很多問題都很自然地符合這個模型:

if car.owner.hasCellPhone then premium += 100;
if car.model.theftRating > 4 then premium += 200;
if car.owner.livesInDodgyArea && car.model.theftRating > 2 then premium += 300;

規則引擎是一種工具,它使得這種計算模型編程變得更容易。它可能是一個完整的開發環境,或者一個可以在傳統平台上工作的框架。生產規則計算模型最適合僅解決一部分計算問題,因此規則引擎可以更好地嵌入到較大的系統中。

你可以自己構建一個簡單的規則引擎。你所需要做的就是創建一組帶有條件和動作的對象,將它們存儲在一個集合中,然後遍歷它們以評估條件並執行這些動作。 

Easy Rules它提供Rule抽象以創建具有條件和動作的規則,並提供RuleEngine API,該API通過一組規則運行以評估條件並執行動作。 

Easy Rules簡單易用,只需兩步:

首先,定義規則,方式有很多種

方式一:註解

@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }
    
    @Action
    public void takeAnUmbrella() {
        System.out.println("It rains, take an umbrella!");
    }
}

方式二:鏈式編程

Rule weatherRule = new RuleBuilder()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when(facts -> facts.get("rain").equals(true))
        .then(facts -> System.out.println("It rains, take an umbrella!"))
        .build();

方式三:表達式

Rule weatherRule = new MVELRule()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when("rain == true")
        .then("System.out.println(\"It rains, take an umbrella!\");");

方式四:yml配置文件

例如:weather-rule.yml

name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
  - "System.out.println(\"It rains, take an umbrella!\");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));

接下來,應用規則

public class Test {
    public static void main(String[] args) {
        // define facts
        Facts facts = new Facts();
        facts.put("rain", true);

        // define rules
        Rule weatherRule = ...
        Rules rules = new Rules();
        rules.register(weatherRule);

        // fire rules on known facts
        RulesEngine rulesEngine = new DefaultRulesEngine();
        rulesEngine.fire(rules, facts);
    }
}

入門案例:Hello Easy Rules

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.0.0</version>
</dependency>

通過骨架創建maven項目:

mvn archetype:generate \
    -DarchetypeGroupId=org.jeasy \
    -DarchetypeArtifactId=easy-rules-archetype \
    -DarchetypeVersion=4.0.0

默認給我們生成了一個HelloWorldRule規則,如下:

package com.cjs.example.rules;

import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Rule;

@Rule(name = "Hello World rule", description = "Always say hello world")
public class HelloWorldRule {

    @Condition
    public boolean when() {
        return true;
    }

    @Action
    public void then() throws Exception {
        System.out.println("hello world");
    }

}

2.  規則定義

2.1.  定義規則

大多數業務規則可以用以下定義表示:

  • Name : 一個命名空間下的唯一的規則名稱
  • Description : 規則的簡要描述
  • Priority : 相對於其他規則的優先級
  • Facts : 事實,可立即為要處理的數據
  • Conditions : 為了應用規則而必須滿足的一組條件
  • Actions : 當條件滿足時執行的一組動作 

Easy Rules為每個關鍵點提供了一個抽象來定義業務規則。

在Easy Rules中,Rule接口代表規則

public interface Rule {

    /**
    * This method encapsulates the rule's conditions.
    * @return true if the rule should be applied given the provided facts, false otherwise
    */
    boolean evaluate(Facts facts);

    /**
    * This method encapsulates the rule's actions.
    * @throws Exception if an error occurs during actions performing
    */
    void execute(Facts facts) throws Exception;

    //Getters and setters for rule name, description and priority omitted.

}

evaluate方法封裝了必須計算結果為TRUE才能觸發規則的條件。execute方法封裝了在滿足規則條件時應該執行的動作。條件和操作由Condition和Action接口表示。

定義規則有兩種方式:

  • 通過在POJO類上添加註解
  • 通過RuleBuilder API編程

可以在一個POJO類上添加@Rule註解,例如:

@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {

    @Condition
    public boolean when(@Fact("fact") fact) {
        //my rule conditions
        return true;
    }

    @Action(order = 1)
    public void then(Facts facts) throws Exception {
        //my actions
    }

    @Action(order = 2)
    public void finally() throws Exception {
        //my final actions
    }

}

@Condition註解指定規則條件
@Fact註解指定參數
@Action註解指定規則執行的動作

RuleBuilder支持鏈式風格定義規則,例如:

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();

組合規則

CompositeRule由一組規則組成。這是一個典型地組合設計模式的實現。

組合規則是一個抽象概念,因為可以以不同方式觸發組合規則。

Easy Rules自帶三種CompositeRule實現:

  • UnitRuleGroup : 要麼應用所有規則,要麼不應用任何規則(AND邏輯)
  • ActivationRuleGroup : 它觸發第一個適用規則,並忽略組中的其他規則(XOR邏輯)
  • ConditionalRuleGroup : 如果具有最高優先級的規則計算結果為true,則觸發其餘規則

複合規則可以從基本規則創建並註冊為常規規則:

//Create a composite rule from two primitive rules
UnitRuleGroup myUnitRuleGroup = new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);

//Register the composite rule as a regular rule
Rules rules = new Rules();
rules.register(myUnitRuleGroup);

RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);

每個規則都有優先級。它代表觸發註冊規則的默認順序。默認情況下,較低的值表示較高的優先級。可以重寫compareTo方法以提供自定義優先級策略。

2.2.  定義事實

在Easy Rules中,Fact API代表事實

public class Fact<T> {
     private final String name;
     private final T value;
}

舉個栗子:

Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);

或者,也可以用這樣簡寫形式

Facts facts = new Facts();
facts.put("foo", "bar");

用@Fact註解可以將Facts注入到condition和action方法中

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }

}

2.3.  定義規則引擎

Easy Rules提供兩種RulesEngine接口實現:

  • DefaultRulesEngine : 根據規則的自然順序應用規則
  • InferenceRulesEngine : 持續對已知事實應用規則,直到不再適用任何規則為止 

創建規則引擎:

RulesEngine rulesEngine = new DefaultRulesEngine();

// or

RulesEngine rulesEngine = new InferenceRulesEngine();

然後,註冊規則

rulesEngine.fire(rules, facts);

規則引擎有一些可配置的參數,如下圖所示:

舉個栗子:

RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

2.4. 定義規則監聽器

通過實現RuleListener接口

public interface RuleListener {

    /**
     * Triggered before the evaluation of a rule.
     *
     * @param rule being evaluated
     * @param facts known before evaluating the rule
     * @return true if the rule should be evaluated, false otherwise
     */
    default boolean beforeEvaluate(Rule rule, Facts facts) {
        return true;
    }

    /**
     * Triggered after the evaluation of a rule.
     *
     * @param rule that has been evaluated
     * @param facts known after evaluating the rule
     * @param evaluationResult true if the rule evaluated to true, false otherwise
     */
    default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }

    /**
     * Triggered on condition evaluation error due to any runtime exception.
     *
     * @param rule that has been evaluated
     * @param facts known while evaluating the rule
     * @param exception that happened while attempting to evaluate the condition.
     */
    default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }

    /**
     * Triggered before the execution of a rule.
     *
     * @param rule the current rule
     * @param facts known facts before executing the rule
     */
    default void beforeExecute(Rule rule, Facts facts) { }

    /**
     * Triggered after a rule has been executed successfully.
     *
     * @param rule the current rule
     * @param facts known facts after executing the rule
     */
    default void onSuccess(Rule rule, Facts facts) { }

    /**
     * Triggered after a rule has failed.
     *
     * @param rule the current rule
     * @param facts known facts after executing the rule
     * @param exception the exception thrown when attempting to execute the rule
     */
    default void onFailure(Rule rule, Facts facts, Exception exception) { }

}

3.  示例

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cjs.example</groupId>
    <artifactId>easy-rules-quickstart</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-core</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-support</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-mvel</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.30</version>
        </dependency>
    </dependencies>
</project>

4.  擴展

規則本質上是一個函數,如y=f(x1,x2,..,xn)

規則引擎就是為了解決業務代碼和業務規則分離的引擎,是一種嵌入在應用程序中的組件,實現了將業務決策從應用程序代碼中分離。

還有一種常見的方式是Java+Groovy來實現,Java內嵌Groovy腳本引擎進行業務規則剝離。

https://github.com/j-easy/easy-rules/wiki

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

EAS:基於網絡轉換的神經網絡結構搜索 | AAAI 2018

論文提出經濟實惠且高效的神經網絡結構搜索算法EAS,使用RL agent作為meta-controller,學習通過網絡變換進行結構空間探索。從指定的網絡開始,通過function-preserving transformation不斷重用其權重,EAS能夠重用之前學習到的知識進行高效地探索新的結構,僅需要10 GPU days即可

來源:曉飛的算法工程筆記 公眾號

論文: Efficient Architecture Search by Network Transformation

  • 論文地址:https://arxiv.org/abs/1707.04873

Introduction

  為了加速神經網絡搜索過程,論文提出EAS(Efficient Architecture Search),meta-controller通過網絡轉化(network transformation)操作進行結構空間探索,操作包含拓寬層,插入層,增加skip-connections等。為了繼續使用學習到的權重,基於function-preserving transformation來初始化新的不同參數的網絡,再進一步訓練來提高性能,能夠顯著地加速訓練過程。對於meta-controller,則結合了最近的強化學習方法

Architecture Search by Net Transformation

  整體算法邏輯如圖1,meta-controller學習如何對當前網絡中進行網絡轉換,為了學習多種網絡轉換操作以及不增加meta-contreoller複雜性,使用encoder network來學習當前網絡的低維表達,然後傳到actor netowrk來生成一個確定的模型轉換動作。為了處理不定長的網絡結構輸入以及考慮整體網絡結構,使用bidrectional recurrent network以及input embedding layer

Actor Networks

  給予輸入結構的低維表達,每個actor network給予特定的網絡轉換動作,共有兩種actor network,分別是Net2Wider actor和Net2Depper

  • Net2Wider Actor

  Net2Wider在保持網絡功能下替換網絡的某一層為更寬的層,例如對於全連接層是增加unit數,對於卷積層是增加卷積核數。對於卷積層的卷積核$K_l$,shape為$(k_wl,k_hl,f_il,f_ol)$,分別代表卷積核寬高以及輸入和輸出的維度數,將當前層替換成更寬的層即$\hat {f}_ol>f_ol$

  首先介紹隨機映射函數$G_l$,可以獲得新卷積核$\hat{K}_l[k_wl,k_hl,f_il,f_jl]$,第一個$f_ol$直接從$K_l$中獲得,剩餘的$\hat{f}_ol-f_o^l$維根據$G_l$從$K_l$中隨機選擇一維,因此,更寬的新層的輸出特徵$\hat{O}_l=O_l(G_l(j))$

  為了保持原有的功能,由於輸入多了複製的部分,下一層的卷積核$K_{l+1}$需要修改,新卷積核$\hat{K}_{l+1}$的shap維$(k_w{l+1},k_h{l+1},\hat{f}_i{l+1}=\hat{f}_ol,f_o^{l+1})$,公式3的意思大概是,權重要除以前一層對應維度複製的次數,以保證$l+1$層輸出跟之前一樣

  為了方便,論文使用的Net2Wider actor同時決定處理的層,對於encoder netowrk輸出的每一層對應的hidden state使用shared sigmoid分類器,另外將卷積的核數或全連接的unit數進行分區,直接將決定的層的對應參數升至下一個區間,例如$32\to 64$

  • Net2Deeper Actor

  Net2DeeperNet操作向網絡中插入新的層,然後將其初始化成跟插入identity mapping一樣,保持其原來的功能。對於新的卷積層,將其卷積核設為identity卷積核,對於全連接層,則將權重矩陣設為identiy矩陣,因此,新層必須與前一層有一樣的核數或unit。另外,想要保持原來網絡的性能,對於激活函數$\phi$,必須滿足$\phi(I\phi(v))=\phi(v)$,ReLU能滿足,而Sigmoid和thnh不能,但仍然可以重用帶sigmoid或tanh激活的網絡的權重,畢竟這樣總比隨機初始化要好。另外,當使用BN時,要設置其scale和bias為undo normalization,而不是初始化為1和0

  Net2Deeper actor的結構如圖3,為一個循環神經網絡,hidden state初始化為encoder network的最後一個hidden state。將CNN結構根據pooling的位置分成多個block,首先確定插入的block,然後再確定插入層的下標,對於新的卷積網絡,agent還需要確定卷積核大小和步長,而對於全連接層則不需要。在卷積網絡中,全連接層需要在網絡的頂層,如果插入位置在全局池化或全連接後面,新層則指定為全連接層

Function-preserving Transformation for DenseNet

  原始的Net2Net設定網絡是layer-to-layer的,沒有并行層,但目前的網絡大都將單層的輸入應用到多個後續的多個層中,直接應用Net2Net會出現問題,因此論文對其進行了改進。對於DenseNet,$l{th}$層會將所有前面的層concatenate成輸入$[O_0,O_1,…,O_{l-1}]$,標記$l{th}$層的卷積核為$K_l$,shape為$(k_wl,k_hl,f_il,f_ol)$
  假設需要拓寬層並保持其功能,首先根據Net2WiderNet操作按公式1和公式2生成新層$\hat{K}_l$,這樣新輸出為$\hat{O}_l=O_l(G_l(j))$,由於$l^{th}$的輸出會傳遞到多層,$\hat{O}_l$的複製會傳遞到後面的所有層,所以要修改所有後續的層

  對於$m{th}>l$層,輸入變為$[O_0,…,O_{l-1},\hat{O}_l,O_{l+1},…,O_{m-1}]$,將隨機映射函數改為公式4,$f_o{0:l}=\sum_{v=0}{l-1}f_ov$為$l^{th}$層的所有輸入數量,公式4的第一部分為$[O_0,…,O_{l-1}]$,第二部分為$\hat{O}l$,第三部分為$[O{l+1},…,O_{m-1}]$

  $\hat{G}m$的簡單示意如上,前面的為新層的index,後面為對應的舊的維度index,然後$m^{th}$層的新權重直接使用替換成$\hat{G}m$的公式3獲得
  假設要在DenseNet的$l{th}$層插入新層,新層輸入為$O_{new}$,輸出為$[O_0,O_1,…,O_l]$。因此,對於$m{th}>l$層,插入后的輸入為$[O_0,…,O
{l},O
{new},O_{l+1},…,O_{m-1}]$,為了按照類似Net2WiderNet那樣保持性能,$O_{new}$應該為$[O_0,O_1,…,O_l]$中的一個複製

  新層的每個卷積核可以表示為tensor$\hat{F}$,shape為$(k_w{new},k_h{new},f_i{new}=f_o{0:l+1})$,第三項為輸入channel數。為了讓$\hat{F}$的輸入為$[O_0,O_1,…,O_l]$的$n^{th}$項,按照類似公式5的方式進行設置(假設卷積寬高為3),其它卷積核設為0,當新層的的輸出設定好后。建立一個特定的隨機映射(這裡是按照層建立映射,前面是按照映射建立層),然後按照公式4和公式3來修改後續的層的卷積核

Experiments and Results

  EAS使用5 GPU進行搜索,有兩種設定,第一種為普通卷積網絡空間,僅包含卷積、池化和全連接,第二種為DenseNet結構空間

Training Details

  meta-controller為單層雙向LSTM,50個hidden units,embedding size為16,使用ADAM進行訓練。每輪通過網絡轉換採樣10個模型,由於重用了特徵,每個模型只訓練20輪(原來為50輪),初始學習率調低為0.02,使用SGD訓練,對準確率$acc$進行線性變化來放大高準確率的收益,例如$tanh(acc_v\times \pi/2)$,另外,每個卷積和全連接後面接ReLU和BN

Explore Plain CNN Architecture Space

  • Start with Small Network

  初始網絡如表1,卷積核大小為${1,3,5 {}}$,卷積核數量為${16,32,64,96,128,192,256,320,384,448,512 }$,全卷積的unit數為${64,128,256,384,512,640,768,896,1024 }$

  訓練分為兩階段,第一階段每輪將起始網絡進行5次Net2Deeper和4次Net2Wider,採樣夠300個網絡后,選取表現最好的網絡訓練100輪作為下階段的輸入。第二階段也是進行5次Net2Deeper和4次Net2Wider,採樣150個網絡后停止,取最好模型進行300輪迭代。結果如表2的depth=16,整體需要10 GPU days,共450個網絡

  • Further Explore Larger Architecture Space

  將上一個實驗的最好模型作為這次實驗的起點,結果如表2的depth=20,另外與SOTA進行了對比,結果如表3

  • Comparison Between RL and Random Search

Explore DenseNet Architecture Space

  將DenseNet-BC(L=40,k=40)作為起點,結果如表4

CONCLUSION

  論文提出經濟實惠且高效的神經網絡結構搜索算法EAS,使用RL agent作為meta-controller,學習通過網絡變換進行結構空間探索。從指定的網絡開始,通過function-preserving transformation不斷重用其權重,EAS能夠重用之前學習到的知識進行高效地探索新的結構,僅需要10 GPU days即可



如果本文對你有幫助,麻煩點個贊或在看唄~
更多內容請關注 微信公眾號【曉飛的算法工程筆記】

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

比埃爾法還大的MPV! 20萬就能買你敢信?

0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。

早些年的時候看到MpV總覺得和麵包車差不多,但是現在的MPV越來越豪華,空間上又有轎車無可比擬的優勢,逐漸成為很多公司和家庭的多用途車首選。

今天我們就為大家帶來福特途睿歐與大眾邁特威的對比評測。

空間

既然是MpV,那麼空間肯定是很重要的部分,你說轎車有多長軸距,在MpV面前全部都是渣渣 。此次試駕的兩台車均為7座車型。坐姿足夠舒服,坐墊支撐到位,腿部空間?自己看!

再到後面,福特途睿歐的第三排座椅擁有三個可獨立調整的靠背,而邁特威第三排的中間座椅就稍微吃虧了,但是可以抽出一個扶手,兩個人乘坐的時候比較舒適。

不過邁特威最爽的用法應該是這樣。

儲物空間

在以往轎車的評測中,我們會到處找放水杯的地方,但是在MPV上真的就是多擔心了,兩台車的空間都是可以用海量來形容,圖中皆為2L大水瓶!!!

福特途睿歐的杯架

邁特威的杯架

除此之外邁特威座椅下方空間還有神奇的儲物空間。途睿歐取而代之的則是獨立的空調出風口。

一般7座SUV如果使用第三排座椅時,後備箱基本上沒有什麼空間,來到MpV上。情況則有所不同。如有需要福特途睿歐還可以將第三排座椅翻起,除了可以開去買菜,心情好還可以開去賣菜。

邁特威的後備箱空間也不俗,不過座椅只能向前移,不能繼續將座椅翻起來。

動力

雖然一台MpV你不指望它能開多快,但是動力就像存款一樣,可以不用,但不能沒有。途睿歐搭載的是福特ECOboost的2.0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。

雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。

而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。加上DQ500的7速雙離合變速箱,動力是足,但是感覺變速箱是刻意放慢了動作以尋求一個平順的效果,大腳油門並不會直接降檔,而是會選擇拉高發動機轉速,並且傳遞到車廂的發動機聲音較大。

配置對比

福特途睿歐配上了這個級別少有的後排天窗。

邁特威則是側滑的玻璃窗。

邁特威兩側都用上了電動門,這一點福特途睿歐上略有遺憾。

舒適性對於MpV同樣重要,福特途睿歐用上了空氣懸挂,這也是福特專門為中國研發的,對於細碎過濾相當到位,國內的這套空氣懸挂是標配,而在英國也只有改款后的頂配車型才配備。

邁特威則是自家的DCC動態懸挂,兩種懸挂相比各有特色。不過有一個細節方面則是搞不懂大眾,這個最常用的扶手,調節起高低真的有點反人類。

總結

邁特威和福特途睿歐相比各有優勢,但是從價格看來,一台邁特威的價格能夠買兩台福特途睿歐了,邁特威(41.88-54.98萬);福特途睿歐(17.69-20.39萬)。質感方面邁特威會更好一些,但是福特途睿歐在如此價位還能取得如此不俗的成績實屬驚人。大家會怎麼選呢?

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

汽車的頭枕為什麼怎麼調都不舒服?

這一后一前兩個猛烈彎折動作,足以導致頸椎折斷。這是重傷或者致死的主要原因。正因為被追尾的主要死/傷原因是揮鞭效應,所以汽車製造商就推出了相關的硬件安全設計。最常見的現行設計就是主動頭枕,防止頸部受傷。主動安全頭枕就是在碰撞的瞬間讓頭部得到支撐,也就是把頭枕往前移動。

很多朋友買來新車以後會裝很多配件,比如座椅套,方向盤套。

而細心的朋友會發現,不管頭枕怎麼調,手動調節還是電動調節都不能調到一個很舒服的位置,總覺得頭靠上去脖子部分空空的,所以很多朋友一般都會額外加裝一個托住脖子的軟墊。難道汽車廠商從來都沒有發現這個問題嗎?

其實汽車頭枕的全稱是汽車座椅安全頭枕,在早些年的時候汽車都沒有頭枕這個東西。

他的出現主要是為提高汽車安全性而設置的一種輔助裝置。

因為在被追尾時, 安全帶是不會發揮作用的.追尾帶來的最大傷害的原因是揮鞭效應, 撞擊一瞬, 被撞者頭部猛烈後仰, 頸椎在巨大的頭部加速作用下,嚴重的頸椎會當場折斷,頭部後仰,在巨大慣性作用下,然後頭部猛烈想前運動,頸椎前折. 這一后一前兩個猛烈彎折動作,足以導致頸椎折斷。

這是重傷或者致死的主要原因.正因為被追尾的主要死/傷原因是揮鞭效應,所以汽車製造商就推出了相關的硬件安全設計。最常見的現行設計就是主動頭枕,防止頸部受傷。

主動安全頭枕就是在碰撞的瞬間讓頭部得到支撐,也就是把頭枕往前移動。其机械結構也不複雜,只要在椅背內設計了一個連桿,發生來自後部的撞擊時,身體重量擠壓在這個連桿上,在聯動機構的作用下,頭枕向前移動,最大限度地防止頭部猛烈後仰,從而保護人員頭部和頸部的安全。

雖然現在也有些廠商將頭枕設計得很舒服,但是汽車的頭枕在絕大多數情況下都只是一項安全配置。

最後祝所有TV的粉絲元旦快樂,在新的一年能開超跑。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

我買中國品牌的車不只是因為便宜!

觀致耗資1600萬與超跑製造商柯尼塞克共同研發的無凸輪軸發動機(代號QamFree),同等排量下,要比傳統的發動機的功率大幅提升45%,最大扭矩大幅提高47%。假如測試通過,進行量產,將成為未來發動機技術的主要發展方向,彷彿為全世界指明了一條比混合動力更為通暢的道路。

自主品牌亮瞎眼睛的地方還多着!

當然了,自主品牌能夠亮瞎眼的地方遠不止這兩點。力壓奔馳、寶馬,在歐洲新車安全評鑒協會Euro-NCAp所進行的2013年最佳碰撞車型評選(小型家用車類)一舉奪得第一名的觀致3,讓一眾歪果仁瞠目結舌。觀致耗資1600萬與超跑製造商柯尼塞克共同研發的無凸輪軸發動機(代號QamFree),同等排量下,要比傳統的發動機的功率大幅提升45%,最大扭矩大幅提高47%。假如測試通過,進行量產,將成為未來發動機技術的主要發展方向,彷彿為全世界指明了一條比混合動力更為通暢的道路。吧唧這麼多,我想說的其實很簡單:自主品牌比你想象中牛逼多了!趕緊去4S店下訂吧!

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

Linux系統如何設置開機自動運行腳本?

大家好,我是良許。

在工作中,我們經常有個需求,那就是在系統啟動之後,自動啟動某個腳本或服務。在 Windows 下,我們有很多方法可以設置開機啟動,但在 Linux 系統下我們需要如何操作呢?

Linux 下同樣可以設置開機啟動,但可能需要我們敲一些命令(可能也有 UI 界面的設置方法,但我不熟,我更多是玩命令)。下面我們就介紹三種簡單但可行的開機啟動設置方法。

方法一:修改 /etc/rc.d/rc.local 文件

/etc/rc.d/rc.local 文件會在 Linux 系統各項服務都啟動完畢之後再被運行。所以你想要自己的腳本在開機后被運行的話,可以將自己腳本路徑加到該文件里。

但是,首先需要確認你有運行這個文件的權限。

$ chmod +x /etc/rc.d/rc.local

為了演示,我們創建了一個腳本,當它被執行之後,將在家目錄下寫入有特定信息的文件。

$ vim auto_run_script.sh

#!/bin/bash
date >> /home/alvin/output.txt
hostname >> /home/alvin/output.txt

保存退出后,再給它賦予可執行權限:

$ chmod +x auto_run_script.sh

然後,我們再將腳本添加到 /etc/rc.d/rc.local 文件最後一行:

$ vim /etc/rc.d/rc.local

/home/alvin/auto_run_script.sh

接下來,我們就可以試試效果了。直接重啟系統就可以了:

$ sudo reboot

重啟之後,就會在家目錄下看到腳本執行的結果了。

方法二:使用 crontab

大家知道,crontab 是 Linux 下的計劃任務,當時間達到我們設定的時間時,可以自動觸發某些腳本的運行。

我們可以自己設置計劃任務時間,然後編寫對應的腳本。但是,有個特殊的任務,叫作 @reboot ,我們其實也可以直接從它的字面意義看出來,這個任務就是在系統重啟之後自動運行某個腳本。

那它將運行的是什麼腳本呢?我們如何去設置這個腳本呢?我們可以通過 crontab -e 來設置。

$ crontab -e

@reboot /home/alvin/auto_run_script.sh

然後,直接重啟即可。運行的效果跟上面類似。

方法三:使用 systemd 服務

以上介紹的兩種方法,在任何 Linux 系統上都可以使用。但本方法僅適用於 systemd 系統。如何區分是不是 systemd 系統?很簡單,只需運行 ps aux 命令,查看 pid 為 1 的進程是不是 systemd 。

為了實現目的,我們需要創建一個 systemd 啟動服務,並把它放置在 /etc/systemd/system/ 目錄下。

我們創建的 systemd 啟動服務如下。請注意,這時後綴是 .service ,而不是 .sh

$ vim auto_run_script.service

[Unit]
Description=Run a Custom Script at Startup
After=default.target

[Service]
ExecStart=/home/alvin/auto_run_script.sh

[Install]
WantedBy=default.target

從服務的內容可以看出來,我們最終還是會調用 /home/alvin/auto_run_script.sh 這個腳本。

然後,我們再把這個腳本放置在 /etc/systemd/systerm/ 目錄下,之後我們再運行下面兩條命令來更新 systemd 配置文件,並啟動服務。

$ systemctl daemon-reload
$ systemctl enable auto_run_script.service

萬事俱備之後,我們就可以重啟系統啦。

$ reboot

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

前端面試手寫篇

手寫篇

1. 手寫 instenceof

原生的instanceof

console.log([] instanceof Array) // true

console.log('' instanceof Array) // false

手寫myInstanceof

function myInstanceof(left,right){
    
    let proto = left.__proto__
    
    let prototype = right.prototype
    
    while(true){
        
        if(proto === null)return false
        
        if(proto === prototype)return true
        
        proto = proto.__proto__
        
    }
}

console.log(myInstanceof([],Array))// true

console.log(myInstanceof('',Array))// false

實現原理:

通過不斷的沿着原型鏈查找,如果找到頂端了即:proto === null,那麼就說明沒有找到,返回false,說明 left 不是 right 構造函數的實例

如果找到隱式原型 proto等於構造函數的原型prototype,那麼說明 leftright 構造函數的實例,返回true

其它情況就是不斷的改變proto,以便可以不斷的往上查找

2. 手寫 flat

原生示例:

const arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

手寫flatDeep:

function flatDeep( arr, dep=1 ){
    let ret = []
    
    for(let i=0;i<arr.length;i++){
        
        if(Array.isArray(arr[i])){
            
            dep>0 ? (ret = ret.concat(flatter(arr[i],dep-1))):(ret.push(arr[i]))
            
        }else{
            
            ret.push(arr[i]) 
        }
    }
    
    return ret
}

實現原理:

第一個參數是數組,第二個是降維層級,

用for循環遍歷這個數組,檢測每一項

如果這項是不是數組則直接添加到ret結果數組裡面

否則根據降維層級判斷,默認是降一維層級,當遞歸降維不滿足ret>0,說明已經達到dep降維層數了,其它情況即ret.push(arr[i])

3. 手寫 call

Function.prototype.myCall = function(context){

    context =(context === null || context === undefined) ? window : context
    
    context.fn = this// 其實就等價於 obj.fn = function say(){} 當指向 context.fn 時,say裏面的this 指向obj [關鍵]
    //obj 此時變成 var obj = {name:'innerName',fn:function say(){console.log(this.name)}}

    let args = [...arguments].slice(1) //截取第二個開始的所有參數
    let result= context.fn(...args)//把執行的結果賦予result變量

    delete context.fn //刪除執行上下文上的屬性 (還原)由var obj = {name:'innerName',fn:function say(){console.log(this.name)}}刪除fn
    return result
}
var name = 'outerName'
var obj = {
    name:'innerName'
}
function say(){
    console.log(this.name)
}
say()//outerName 等價於 window.say this指向window
say.myCall(obj)//innerName

實現原理:

函數的原型方法call 第一個參數是傳入的執行上下文,後面傳入的都是參數,以逗號隔開

當傳入的是null或undefined是執行上下文是指向window,否使為傳入的對象,然後再傳入的對象身上添加fn屬性並把函數實例say函數賦值給fn,此時變成

var obj = {name:'innerName',fn:function say(){console.log(this.name)}}此時context就是obj對象啦,所有你執行context.fn(...args)

其實就是obj.fn(...args)fn 其值是 function say(){ console.log(this.name) },所以這個this就變成obj對象了

然後就是結果賦值,對象還原

返回結果

4. 手寫 apply

Function.prototype.myApply = function(context){
    
    context =(context === null || context === undefined) ? window : context
    
    let result
    
    context.fn = this
    
    result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
    
    delete context.fn
    
    return result
}

myCall實現原理大致相同,不同的是由於callapply的傳參方式不一樣,

我們需要額外的對第二個參數做判斷,apply受參形式是數組,且再第二個參數位置,

一:如果第二個參數存在,執行的時候就把第二個參數(數組形式)用擴展運算符打散後傳入執行

二:如果第二個參數不存在,執行執行

其它就於call的實現一樣

5. 手寫 bind

Function.prototype.myBind = function(context){
    
    context =(context === null || context === undefined) ? window : context
    
    let o = Object.create(context)
    
    o.fn = this
    
    let args = [...arguments].slice(1)
    
    let fn= function(){
        
        o.fn(...args)
    }
    
    return fn
}

bind 的手寫實現,與其它兩個區別是返回一個函數,並沒返回函數執行的結果,並且受參形式不受限制

實現原理:

通過 Object.create方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__,通過 中介對象o來實現,來達到不影響傳入的對象

6. 手寫 new

new 一個函數的時候,會生成一個實例,該實例的隱式原型__proto__ ===該函數的prototype原型對象

在構造函數中this指向當前實例

最後再將實例對象返回

function myNew(func){
    
    //第一步 將函數的 prototype 指向 o 對象的__proto__
    let o = Object.create(func.prototype)
    
    //第二步 通過call改變 this的指向,使之指向 o
    let ret = func.call(o)
    
    //第三步 如果構造函數裏面有返回對象,則返回這個對象,沒有則返回 o 對象
    return typeof ret === 'object' ? ret : o

}

檢測:

function M(){}

let m = myNew(M); // 等價於 new M 這裏只是模擬
console.log(m instanceof M); // instanceof 檢測實例
console.log(m instanceof Object);
console.log(m.__proto__.constructor === M);

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

C# 反射與特性(十):EMIT 構建代碼

目錄

  • 構建代碼
    • 1,程序集(Assembly)
    • 2,模塊(Module)
    • 3,類型(Type)
    • 4,DynamicMethod 定義方法與添加 IL

前面,本系列一共寫了 九 篇關於反射和特性相關的文章,講解了如何從程序集中通過反射將信息解析出來,以及實例化類型。

前面的九篇文章中,重點在於讀數據,使用已經構建好的數據結構(元數據等),接下來,我們將學習 .NET Core 中,關於動態構建代碼的知識。

其中表達式樹已經在另一個系列寫了,所以本系列主要是講述 反射,Emit ,AOP 等內容。

如果現在總結一下,反射,與哪些數據結構相關?

我們可以從 AttributeTargets 枚舉中窺見:

public enum AttributeTargets
{
   All=16383,
   Assembly=1,
   Module=2,
   Class=4,
   Struct=8,
   Enum=16,
   Constructor=32,
   Method=64,
   Property=128,
   Field=256,
   Event=512,
   Interface=1024,
   Parameter=2048,
   Delegate=4096,
   ReturnValue=8192
}

分別是程序集、模塊、類、結構體、枚舉、構造函數、方法、屬性、字段、事件、接口、參數、委託、返回值。

以往的文章中,已經對這些進行了很詳細的講解,我們可以中反射中獲得各種各樣的信息。當然,我們也可以通過動態代碼,生成以上數據結構。

動態代碼的其中一種方式是表達式樹,我們還可以使用 Emit 技術、Roslyn 技術來編寫;相關的框架有 Natasha、CS-Script 等。

構建代碼

首先我們引入一個命名空間:

using System.Reflection.Emit;

Emit 命名空間中裏面有很多用於構建動態代碼的類型,例如 AssemblyBuilder,這個類型用於構建程序集。類推,構建其它數據結構例如方法屬性,則有 MethodBuilderPropertyBuilder

1,程序集(Assembly)

AssemblyBuilder 類型定義並表示動態程序集,它是一個密封類,其定義如下:

public sealed class AssemblyBuilder : Assembly

AssemblyBuilderAccess 定義動態程序集的訪問模式,在 .NET Core 中,只有兩個枚舉:

枚舉 說明
Run 1 可以執行但無法保存該動態程序集。
RunAndCollect 9 當動態程序集不再可供訪問時,將自動卸載該程序集,並回收其內存。

.NET Framework 中,有 RunAndSave 、Save 等枚舉,可用於保存構建的程序集,但是在 .NET Core 中,是沒有這些枚舉的,也就是說,Emit 構建的程序集只能在內存中,是無法保存成 .dll 文件的。

另外,程序集的構建方式(API)也做了變更,如果你百度看到文章 AppDomain.CurrentDomain.DefineDynamicAssembly,那麼你可以關閉創建了,說明裡面的很多代碼根本無法在 .NET Core 下跑。

好了,不再贅述,我們來看看創建一個程序集的代碼:

            AssemblyName assemblyName = new AssemblyName("MyTest");
            AssemblyBuilder assBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

構建程序集,分為兩部分:

  • AssemblyName 完整描述程序集的唯一標識。
  • AssemblyBuilder 構建程序集

一個完整的程序集,有很多信息的,版本、作者、構建時間、Token 等,這些可以使用

AssemblyName 來設置。

一般一個程序集需要包含以下內容:

  • 簡單名稱。
  • 版本號。
  • 加密密鑰對。
  • 支持的區域性。

你可以參考以下示例:

            AssemblyName assemblyName = new AssemblyName("MyTest");
            assemblyName.Name = "MyTest";   // 構造函數中已經設置,此處可以忽略

            // Version 表示程序集、操作系統或公共語言運行時的版本號.
            // 構造函數比較多,可以選用 主版本號、次版本號、內部版本號和修訂號
            // 請參考 https://docs.microsoft.com/zh-cn/dotnet/api/system.version?view=netcore-3.1
            assemblyName.Version = new Version("1.0.0");
            assemblyName.CultureName = CultureInfo.CurrentCulture.Name; // = "zh-CN" 
            assemblyName.SetPublicKeyToken(new Guid().ToByteArray());

最終程序集的 AssemblyName 显示名稱是以下格式的字符串:

Name <,Culture = CultureInfo> <,Version = Major.Minor.Build.Revision> <, StrongName> <,PublicKeyToken> '\0'

例如:

ExampleAssembly, Version=1.0.0.0, Culture=en, PublicKeyToken=a5d015c7d5a0b012

另外,創建程序集構建器使用 AssemblyBuilder.DefineDynamicAssembly() 而不是 new AssemblyBuilder()

2,模塊(Module)

程序集和模塊之間的區別可以參考

https://stackoverflow.com/questions/9271805/net-module-vs-assembly

https://stackoverflow.com/questions/645728/what-is-a-module-in-net

模塊是程序集內代碼的邏輯集合,每個模塊可以使用不同的語言編寫,大多數情況下,一個程序集包含一個模塊。程序集包括了代碼、版本信息、元數據等。

MSDN指出:“模塊是沒有 Assembly 清單的 Microsoft 中間語言(MSIL)文件。”。

這些就不再扯淡了。

創建完程序集后,我們繼續來創建模塊。

            AssemblyName assemblyName = new AssemblyName("MyTest");
            AssemblyBuilder assBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            ModuleBuilder moduleBuilder = assBuilder.DefineDynamicModule("MyTest");             // ⬅

3,類型(Type)

目前步驟:

Assembly -> Module -> Type 或 Enum

ModuleBuilder 中有個 DefineType 方法用於創建 classstructDefineEnum方法用於創建 enum

這裏我們分別說明。

創建類或結構體:

TypeBuilder typeBuilder = moduleBuilder.DefineType("MyTest.MyClass",TypeAttributes.Public);

定義的時候,注意名稱是完整的路徑名稱,即命名空間+類型名稱。

我們可以先通過反射,獲取已經構建的代碼信息:

            Console.WriteLine($"程序集信息:{type.Assembly.FullName}");
            Console.WriteLine($"命名空間:{type.Namespace} , 類型:{type.Name}");

結果:

程序集信息:MyTest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
命名空間:MyTest , 類型:MyClass

接下來將創建一個枚舉類型,並且生成枚舉。

我們要創建一個這樣的枚舉:

namespace MyTest
{
    public enum MyEnum
    {
        Top = 1,
        Bottom = 2,
        Left = 4,
        Right = 8,
        All = 16
    }
}

使用 Emit 的創建過程如下:

EnumBuilder enumBuilder = moduleBuilder.DefineEnum("MyTest.MyEnum", TypeAttributes.Public, typeof(int));

TypeAttributes 有很多枚舉,這裏只需要知道聲明這個枚舉類型為 公開的(Public);typeof(int) 是設置枚舉數值基礎類型。

然後 EnumBuilder 使用 DefineLiteral 方法來創建枚舉。

方法 說明
DefineLiteral(String, Object) 在枚舉類型中使用指定的常量值定義命名的靜態字段。

代碼如下:

            enumBuilder.DefineLiteral("Top", 0);
            enumBuilder.DefineLiteral("Bottom", 1);
            enumBuilder.DefineLiteral("Left", 2);
            enumBuilder.DefineLiteral("Right", 4);
            enumBuilder.DefineLiteral("All", 8);

我們可以使用反射將創建的枚舉打印出來:

        public static void WriteEnum(TypeInfo info)
        {
            var myEnum = Activator.CreateInstance(info);
            Console.WriteLine($"{(info.IsPublic ? "public" : "private")} {(info.IsEnum ? "enum" : "class")} {info.Name}");
            Console.WriteLine("{");
            var names = Enum.GetNames(info);
            int[] values = (int[])Enum.GetValues(info);
            int i = 0;
            foreach (var item in names)
            {
                Console.WriteLine($" {item} = {values[i]}");
                i++;
            }
            Console.WriteLine("}");
        }

Main 方法中調用:

 WriteEnum(enumBuilder.CreateTypeInfo());

接下來,類型創建成員,就複雜得多了。

4,DynamicMethod 定義方法與添加 IL

下面我們來為 類型創建一個方法,並通過 Emit 向程序集中動態添加 IL。這裏並不是使用 MethodBuider,而是使用 DynamicMethod。

在開始之前,請自行安裝反編譯工具 dnSpy 或者其它工具,因為這裏涉及到 IL 代碼。

這裏我們先忽略前面編寫的代碼,清空 Main 方法。

我們創建一個類型:

    public class MyClass{}

這個類型什麼都沒有。

然後使用 Emit 動態創建一個 方法,並且附加到 MyClass 類型中:

            // 動態創建一個方法並且附加到 MyClass 類型中
            DynamicMethod dyn = new DynamicMethod("Foo",null,null,typeof(MyClass));
            ILGenerator iLGenerator = dyn.GetILGenerator();

            iLGenerator.EmitWriteLine("HelloWorld");
            iLGenerator.Emit(OpCodes.Ret);

            dyn.Invoke(null,null);

運行後會打印字符串。

DynamicMethod 類型用於構建方法,定義並表示可以編譯、執行和丟棄的一種動態方法。 丟棄的方法可用於垃圾回收。。

ILGenerator 是 IL 代碼生成器。

EmitWriteLine 作用是打印字符串,

OpCodes.Ret 標記 結束方法的執行,

Invoke 將方法轉為委託執行。

上面的示例比較簡單,請認真記一下。

下面,我們要使用 Emit 生成一個這樣的方法:

        public int Add(int a,int b)
        {
            return a + b;
        }

看起來很簡單的代碼,要用 IL 來寫,就變得複雜了。

ILGenerator 正是使用 C# 代碼的形式去寫 IL,但是所有過程都必須按照 IL 的步驟去寫。

其中最重要的,便是 OpCodes 枚舉了,OpCodes 有幾十個枚舉,代表了 IL 的所有操作功能。

請參考:https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.emit.opcodes?view=netcore-3.1

如果你點擊上面的鏈接查看 OpCodes 的枚舉,你可以看到,很多 功能碼,這麼多功能碼是記不住的。我們現在剛開始學習 Emit,這樣就會難上加難。

所以,我們要先下載能夠查看 IL 代碼的工具,方便我們探索和調整寫法。

我們看看此方法生成的 IL 代碼:

  .method public hidebysig instance int32
    Add(
      int32 a,
      int32 b
    ) cil managed
  {
    .maxstack 2
    .locals init (
      [0] int32 V_0
    )

    // [14 9 - 14 10]
    IL_0000: nop

    // [15 13 - 15 26]
    IL_0001: ldarg.1      // a
    IL_0002: ldarg.2      // b
    IL_0003: add
    IL_0004: stloc.0      // V_0
    IL_0005: br.s         IL_0007

    // [16 9 - 16 10]
    IL_0007: ldloc.0      // V_0
    IL_0008: ret

  } // end of method MyClass::Add

看不懂完全沒關係,因為筆者也看不懂。

目前我們已經獲得了上面兩大部分的信息,接下來我們使用 DynamicMethod 來動態編寫方法。

定義 Add 方法並獲取 IL 生成工具:

            DynamicMethod dynamicMethod = new DynamicMethod("Add",typeof(int),new Type[] { typeof(int),typeof(int)});
            ILGenerator ilCode = dynamicMethod.GetILGenerator();

DynamicMethod 用於定義一個方法;ILGenerator是 IL 生成器。當然也可以將此方法附加到一個類型中,完整代碼示例如下:

            // typeof(Program),表示將此動態編寫的方法附加到 MyClass 中
            DynamicMethod dynamicMethod = new DynamicMethod("Add", typeof(int), new Type[] { typeof(int), typeof(int) },typeof(MyClass));


            ILGenerator ilCode = dynamicMethod.GetILGenerator();

            ilCode.Emit(OpCodes.Ldarg_0); // a,將索引為 0 的自變量加載到計算堆棧上。
            ilCode.Emit(OpCodes.Ldarg_1); // b,將索引為 1 的自變量加載到計算堆棧上。
            ilCode.Emit(OpCodes.Add);     // 將兩個值相加並將結果推送到計算堆棧上。

            // 下面指令不需要,默認就是彈出計算堆棧的結果
            //ilCode.Emit(OpCodes.Stloc_0); // 將索引 0 處的局部變量加載到計算堆棧上。
            //ilCode.Emit(OpCodes.Br_S);    // 無條件地將控制轉移到目標指令(短格式)。
            //ilCode.Emit(OpCodes.Ldloc_0); // 將索引 0 處的局部變量加載到計算堆棧上。

            ilCode.Emit(OpCodes.Ret);     // 即 return,從當前方法返回,並將返回值(如果存在)從被調用方的計算堆棧推送到調用方的計算堆棧上。

            // 方法1
            Func<int, int, int> test = (Func<int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<int, int, int>));
            Console.WriteLine(test(1, 2));

            // 方法2
            int sum = (int)dynamicMethod.Invoke(null, BindingFlags.Public, null, new object[] { 1, 2 }, CultureInfo.CurrentCulture);
            Console.WriteLine(sum);

實際以上代碼與我們反編譯出來的 IL 編寫有所差異,具體俺也不知道為啥,在群里問了調試了,註釋掉那麼幾行代碼,才通過的。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

深入理解JVM(③)各種垃圾收集算法

前言

從如何判定對象消亡的角度出發,垃圾收集算法可以劃分為“引用計數式垃圾收集”(Reference Counting GC)和“追蹤式垃圾收集”(Tracing GC)兩大類,這兩類也常被稱作“直接垃圾收集”和“間接垃圾收集”。由於主流Java虛擬機中使用 的都是“追蹤式垃圾收集”,所以後續介紹的垃圾收集算法都是屬於追蹤式的垃圾收集。

分代式收集理論

當前商業虛擬機的垃圾收集器,大多數都遵循了“分代收集”的理論進行設計。
主要簡歷在兩個分代假說之上:
1、弱分代假說:絕大多數對象都是“朝生夕滅”的。
2、強分代假說:熬過越多此垃圾收集過程的對象就越難以消亡。
這兩個分代假說奠定了多款常用的垃圾收集器的一致設計原則:收集器應該將Java堆劃分出不同的區域,然後將回收對象依據其年齡(對象熬過垃圾收集過程的次數)分配到不同的區域之中存儲。
把分代收集理論具體放到現在商用的Java虛擬機里,設計者一般至少會把Java堆劃分為新生代(Young Generation) 和 老年代(Old Generation兩個區域。在新生代中,每次垃圾收集時都有大批對象死去,而每次回收后存活的少量對象,將會逐步晉陞到老年代中存放。

標記-清除算法

標記-清除算法,分為“標記”和“清除”兩個階段:首先標記所有需要回收的對象,標記完成后,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回收所有未被標記的對象。
這個算法有兩個主要的缺點:
第一個是執行效率不穩定,如果Java堆中有大部分是需要回收的對象,這個會進行大量標記和清除動作,導致標記和清除兩個過程的執行效率隨着對象數量增長而降低。
第二個是內存碎片化問題,標記、清除之後會產生大量不連續的內存碎片,空間碎片太多會導致當需要大對象時找不到足夠的連續內存,而提前觸發另一次垃圾收集動作。
因為這兩個缺點的原因,才會產生後續一些針對於修復這兩個缺點的算法。
標記清除算法示意圖:

標記複製算法

標記複製算法也被簡稱Wie複製算法,為了解決標記清除算法面對大量可回收對象時執行效率低的問題,而產生的一種稱為“半區複製”的垃圾收集算法。
原理是:將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊當這一塊內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。
這種算法不用考慮空間碎片化,只需要移動堆指針,按順序分配即可,實現簡單,運行高效,但缺點也是顯而易見的,就是將可用內存縮小了原來的一半。
標記複製算法示意圖:

由於新生代里的對象“朝生夕滅”,針對這個特點,又產生了一種更優化的半區複製分代策略,稱為“Appel式回收”。具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只是用Eden和其中一塊Survivor。當發生垃圾收集時,將Eden和Survivor中任然存活的對象一次性複製到另外一塊Survivor空間上,然後直接清理掉Eden和Survivor空間。
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是說每次可利用的空間為新生代的90%,只有10%的空間會暫時“浪費”。
如果另外一塊兒Survivor沒有足夠的空間存放存活的對象了,這些對象將通過分配擔保機制直接進入到老年代。

標記整理算法

標記複製算法在對象存活率較高時就要進行較多的複製操作,效率將會降低。更關鍵的是,如果不浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
針對老年代對象的存亡特徵,產生了另外一種有針對性的“標記整理”算法。標記的過程和“標記-清除”算法一樣,也是判斷對象是否屬於垃圾的過程。但後續步驟是讓所有存活的對象都向內存空間一端移動,然後直接清理掉邊界以外的內存。
標記整理算法示意圖:

在這種算法中,在移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新所有引用這些對象的地方將會是一種極為負重的操作,而且這種移動操作必須在暫停用戶應用程序才能進行(也就是“Stop The World”)。但是不移動又會造成內存空間碎片化。所以各有利弊,從垃圾收集的停頓時間來看,不移動對象停頓時間更短,但從整個程序的吞吐量來看,移動對象會更划算。所以要依情況而定。
還有一種“和稀泥”的解決方案,就是平時採用標記清除算法,直到內存空間碎片化程度已經大到影響對象分配時,再採用標記整理算法收集一次,以獲得規整的內存空間。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!