網絡編程-Netty-Reactor模型

目錄

  • # 摘要
  • 高性能服務器
  • Reactor模式
    • Reactor單線程模型設計
    • Reactor多線程模型設計
    • 主從Reactor多線程模型設計
    • Netty Reactor模型設計
  • 參考
  • 你的鼓勵也是我創作的動力
  • Posted by 微博@Yangsc_o
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

# 摘要

在前兩篇《快速理解Linux網絡I_O》、《java的I_O模型-BIO&NIO&AIO》兩邊中介紹了Linux下的I/O模型和java中的I/O模型,今天我們介紹Reactor模型,並探究Netty的實現

高性能服務器

在互聯網時代,我們使用的軟件基本上全是C/S架構,C/S架構的軟件一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。C/S架構可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務器):服務器管理某種資源,並且通過操作這種資源來為它的客戶端提供某種服務

那服務器如何能快速的處理用戶的請求呢?在我看來高性能服務器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基於此服務器進行業務開發需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態

而滿足如上需求的一個基礎就是高性能的IO!

Reactor模式

什麼是Reactor模式?

兩種I/O多路復用模式:Reactor和Proactor,兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

在Proactor模式中,處理器–或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩衝區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。

說人話的方式理解:

  • reactor:能收了你跟俺說一聲。
  • proactor: 你給我收十個字節,收好了跟俺說一聲。

Doug Lea是這樣類比的

  • Reactor通過調度適當的處理程序來響應IO事件;
  • 處理程序執行非阻塞操作
  • 通過將處理程序綁定到事件來管理;

Reactor單線程模型設計

單線程版本Java NIO的支持:

  • Channels:與支持非阻塞讀取的文件,套接字等的連接

  • Buffers:類似於數組的對象,可由Channels直接讀取或寫入

  • Selectors:通知一組通道中哪一個有IO事件

  • SelectionKeys:維護IO事件狀態和綁定

  • Reactor 代碼如下

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        key.attach(new Acceptor());
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    dispatch(selectionKey);
                }
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    new Handler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(
                new Reactor(1234)
        ).start();
    }

}
  • Handler代碼如下:
public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }


    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                System.out.println("接收到來自客戶端(" + socketChannel.socket().getInetAddress().getHostAddress()
                        + ")的消息:" + new String(inputBuffer.array()));
                seletionKey.attach(new Sender());
                seletionKey.interestOps(SelectionKey.OP_WRITE);
                seletionKey.selector().wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

}

這個模型和上面的NIO流程很類似,只是將消息相關處理獨立到了Handler中去了!雖然說到NIO一個線程就可以支持所有的IO處理。但是瓶頸也是顯而易見的!如果這個客戶端多次進行請求,如果在Handler中的處理速度較慢,那麼後續的客戶端請求都會被積壓,導致響應變慢!所以引入了Reactor多線程模型!

Reactor多線程模型設計

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!

Reactor保持不變,僅需要改動Handler代碼:

public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;
    private Selector selector;


    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.selector = selector;
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }

    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                executorService.execute(new Processer());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

    synchronized void processAndHandOff() {
        process();
        // or rebind attachment
        state = SENDING;
        seletionKey.interestOps(SelectionKey.OP_WRITE);
        selector.wakeup();
    }

    class Processer implements Runnable {
        @Override
        public void run() {
            processAndHandOff();
        }
    }

}

主從Reactor多線程模型設計

主從Reactor多線程模型是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同:

Handler保持不變,僅需要改動Reactor代碼:

public class Reactor {
    // also create threads
    Selector[] selectors;
    AtomicInteger next = new AtomicInteger(0);
    final ServerSocketChannel serverSocketChannel;

    private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;

    public Reactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selectors = new Selector[4];
        for (int i = 0; i < selectors.length; i++) {
            Selector selector = selectors[i];
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            key.attach(new Acceptor());
            new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        selector.select();
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            dispatch(selectionKey);
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }



    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Reactor(1234);
    }

}

以上是三種不同的設計思路,接下來看一下Netty這個一個高性能NIO框架,其是如何實現Reactor模型的!

Netty Reactor模型設計

  • 看一個最簡單的Netty服務端代碼
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務器代碼來看,與Reactor模型進行對應!

  • EventLoopGroup就相當於是Reactor,bossGroup對應主Reactor,workerGroup對應從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel,非child開頭的方法配置的是服務端channel

當然Netty的線程模型並不是固定的,它支持Reactor單線程模型、Reactor多線程模型、主從模型,上面的例子是一個主從模型的,下面進行詳細的分析,如圖所示:

服務啟動時,創建了兩個EventLoopGroup,它們實際上是兩個Reactor線程池,一個用於接收TCP連接、一個用於處理I/O相關的讀寫操作、或者執行系統task、定時task等;

  • Netty用於接收客戶端請求連接池職責如下:
    • 接收客戶端請求並初始化channel參數;
    • 講鏈路變更事件通知給ChannelPipiline;
  • Netty用於處理I/O連接池職責如下:
    • 異步讀取通信對端的數據報,發送讀事件到ChannelPipiline;
    • 異步發送消息對端的數據報,調用ChannelPipiline的消息發送接口;
    • 執行系統調用task;
    • 執行系統定時任務task,例如鏈路空閑狀態檢測定時任務;

參考

Scalable IO in Java

高性能Server—Reactor模型

NIO技術概覽

《Netty 權威指南》第二版 — 李林峰

你的鼓勵也是我創作的動力

打賞地址

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

python3 源碼閱讀-虛擬機運行原理

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterState和PyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

※網頁設計最專業,超強功能平台可客製化

Google 推出「碳智慧運算平台」 讓資料中心根據天氣調配綠能使用

摘錄自2020年5月26日 科技報橘報導

Google 已在全球建立 20 個資料中心。Google 為達到讓自己的資料中心的運作朝向零碳排放的長期目標,近期展開了新的計畫,成了立一組團隊推出「碳智慧運算平台」,並與丹麥綠能科技公司 Tomorrow 合作,透過天氣預測即時調配何時適合使用再生能源,讓資料中心能充分利用、處理能再生能源的電力來運作資料運行。

Google 表示,這個平台不需要增外的硬體運作,也不會影響 Google 本身既有的服務,會受調整的資料運行工作是如 Google Photo 濾鏡的新功能的製作,或是增加 Google 翻譯的新單字等等,透過減少碳足跡,來讓 24 小時無碳能源供應的願景更近一步。

Google 透過調節資料運作時間來減少碳排放的計畫聽起來新穎且聰慧,不過 Google 尚未提供相關研究成果的詳細數據,根據 Techchruch 的報導,該計劃的技術代表 Ana Radovanovic 表示,初步的結果證明這樣的方式是有效的,不過資料運算轉移方法與觀測成效等相關研究結果,要等到預計今年稍後才會進行發表。

能源議題
能源節約
能源轉型
國際新聞
美國
綠能
智慧型節能
零碳排放

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

【其他文章推薦】

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

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

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

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

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

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

巴西肉品包裝廠成病毒溫床 2400員工染疫!

摘錄自2020年6月02日ETtoday報導

巴西勞工檢察官辦公室今(2日)表示,境內最南端的南大河州累計武漢肺炎(COVID-19)確診病例約9000例,其中超過1/4為肉品工廠工人。

路透社報導,巴西勞工檢察官聲明表示,約有2399名分散在南大河州(Rio Grande do Sul)18個城市、24座屠宰場的員工遭到感染。根據巴西衛生部統計數據,這個數字相當於南大河州累計到昨天為止共9332起病例的25.7%。

這項發現證實肉品包裝工廠已成巴西疫情溫床。巴西肉品包裝業巨頭JBS SA和BRF SA都在南大河州設有工廠。兩家公司位於當地的工廠先前都曾因爆發新型冠狀病毒疫情而暫時關閉。

生活環境
國際新聞
巴西
武漢肺炎
群聚感染
肉品加工

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

【其他文章推薦】

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

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

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

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

※網頁設計最專業,超強功能平台可客製化

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

地球生態亮紅燈!最深海溝驚見「新物種」 體內藏大量塑膠纖維

摘錄自2020年6月3日CTWANT報導

太平洋馬里亞納海溝(Mariana Trench)是全球最深的海溝,其海平面深度甚至大於聖母峰的高度,在如此神秘的海域中,卻發現了一項全新物種,經研究過後,竟發現牠的體內充滿了塑膠微粒,可見塑膠的禍害已經嚴重污染地球生態,就連海洋深處也遭受其害。

綜合外媒報導,英國紐卡索大學(Newcastle University)在海平面下近7公里處發現新物種,並採集4隻帶回研究分析,沒想到竟在其中1隻體內發現塑膠纖維,與寶特瓶材質「聚對苯二甲酸乙二酯(PET)」相似度高達60%,因此將這新物種命名為「塑膠鉤蝦(Eurythenes plasticus)」。

世界自然基金會(WWF)德國海洋計畫主任維斯普(HeikeVesper)強調,這項驚人的發現證明了「廢棄塑膠」已經嚴重污染海洋,儘管物質已變成細小微粒,但還是充斥在空氣、飲用水之中。這項研究已刊登在動物生物分類學科學期刊《Zootaxa》,警示人們應立即採取行動,阻止傷害繼續發生。

生態保育
海洋
公害污染
污染治理
生物多樣性
國際新聞
馬里亞納海溝
新物種
海洋汙染
廢棄物

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

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

※網頁設計最專業,超強功能平台可客製化

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

長灘島上幾乎沒有蝙蝠了 菲律賓度假勝地的生態危機:狐蝠族群消逝中

環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

※網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

新能源曙光 科學家開發出鈉電池

摘錄自2020年6月1日中時電子報報導

華盛頓州立大學(WSU)和西北太平洋國家實驗室(PNNL)的研究人員,創建了一種鈉離子電池,有望成為新一代的充電電池,而且比現行的鋰離子電池有個巨大的優勢,就是原料相當廉價。

科技探險(techxplore)報導,功效良好的鋰離子電池,幾乎可說「改變了整個時代」,它幾乎達到「無處不在」的地步,舉凡手機、筆記型電腦、和電動汽車,都需要用到鋰離子電池。但是鋰離子電池一直都有個隱憂,就是稀缺。鋰電池的材料來自鈷、鋰等稀少、昂貴的材料製成,所以未來這些材料將變得更難獲得,有朝一日可能會遇到材料枯竭的危機,因此鈉電池就是一種希望。

美國能源部電力辦公室儲能研究室主管伊默 ‧古克博士(Imre Gyuk)說:「這是鈉離子電池的一項重大發展,人們會希望用鈉離子電池,代替鋰離子電池。」

能源議題
能源轉型
國際新聞
電池
鋰電池

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

首度超車燃煤 再生能源躍美主要電力

摘錄自2020年6月1日工商時報報導

根據能源資訊管理局(EIA)的調查指出,2019年美國再生能源發電量創下1885年來首次超越燃煤紀錄,這也意味再生能源已成為美國主要電力來源。

EIA表示,去年燃煤發電量不僅較前一年減少15%至11.3千兆英制熱單位,創下1964年來最低,並且為連續第六季下滑。同時期再生能源發電量則是年增1%到11.5千兆英制熱單位,寫下紀錄新高。

當中又以風力與太陽能發電量出現最大成長,目前在美國再生能源最常被使用的風力發電,在去年已首度超越水力發電。

再生能源
能源議題
能源轉型
國際新聞
美國
發電量

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

※台中搬家公司費用怎麼算?

自從開上了寶駿310,媽媽再也不擔心我養不起車!

寶駿的保養成本,每天只需1。9元對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

誰又會想過一部IpHONE 7的價格能完成一輛車的首付。至少在寶駿310面世以前,沒有人去假設過這一可能性。3.68萬的起售價,4.98萬的頂配封頂,寶駿310讓“年輕人的第一輛車“的門檻降低到了讓人詫異的地步。而在頗有吸引力的價格背後,我們看到了年輕動感的設計、豐富多元的配置、寬敞舒適的空間,這也直接造就了寶駿310在11月收穫高達15006輛的銷量。“年輕人的第一輛車”這個稱號,頗有幾分份量。

不過很顯然,寶駿310希望把“年輕人的第一輛車”這個名號更徹底地貫徹下去。所以在用車成本這項決定着用戶是“用一輛車”還是“養一輛車”的問題上,寶駿310在整個產品的生命周期內為年輕人提供了幾乎零壓力的用車成本。對於尚處於奮鬥的年輕人來說:沒有比這更讓人愉快的事情了。

超長的保修政策!完全沒有後顧之憂!

保修期的重要性,我想每一個人都很清楚。小到手機,大到冰箱,再到汽車,無一例外。這不僅是關乎消費者對產品本身的信心,更重要的是:萬一產品因為質量而壞了,消費者能夠找到一個可靠的人來把它免費修好。而寶駿310,也堪稱豪爽地給出了2年或5萬公里的三包有效期以及5年10萬公里的整車保修期。

正所謂沒有對比就沒有傷害。本田飛度的整車保修期為3年或10萬公里;豐田卡羅拉的整車保修期為3年或10萬公里。寶駿310的整車保修期足足比這些主流的合資車型多出了2年的時間。2年的時間能夠做些什麼?或許你能夠成為老闆;或者結婚成為愛情奴;又或者把歐洲都走上一遍。而這兩年的時間,你壓根不用擔心你的寶駿310。

而更貼心的是,寶駿310的免費首保服務(3個月或5000公里)包含了機油更換、機油濾芯更換以及工時費在內的全部保養費用。首保,沒有任何一分的費用。

修不起車?!寶駿的保養成本,每天只需1.9元

對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

按照寶駿310維修保養手冊的標準,寶駿310三年下來的正常養護費用只需要2128元(按每年行駛15000公里為標準)。倘若把筆養護費用按天來計算,呵呵,那真是嚇壞寶寶—1.9元。相信我,這不是電視購物的忽悠。

加不起油?!寶駿310的油費低到你不信

比起正常養護,或許油費更能引起大家的關注,畢竟加油的頻率要比養護要高太多。不過比起其他車主對於油價的膽顫心驚,寶駿310車主絕對要淡定許多。原因無他,寶駿310搭載了一台高效的1.2L自然吸氣四缸發動機(最大馬力82ps,最大扭矩116Nm),並且配備了一台5檔的手動變速箱。按照寶駿310在工信部的認定油耗來計算(5.3L/百公里,實際車主反映也符合這一數據),三年下來(每年行駛15000公里),寶駿310的油耗只需要14167元(92號汽油)。對比起那些油耗動輒8L、9L的轎車、SUV來說,寶駿310的油耗恐怕不是一個省字能夠概括。所以我想那些寶駿310的車主一定都是些喜歡四處亂逛的人,畢竟他們都不需要擔心油費。

媽媽再也不用擔心我買不起保險了

寶駿310的價格優勢除了直接拉低了年輕人的購買門檻以外,也使得年輕人許多負擔的車險費用大大降低。以售價4.98萬的寶駿310 【2016款 1.2L 手動豪華型】為例,在第三者50萬以及險種齊全的情況下,交強險加上商業險的總費用只需要5000元/一年。而且按照現在的保險法,假如車主出險的次數較少,這一商業險費用還有很大的下降空間。

沒有車比寶駿310更勝任“年輕人的第一台車”

很明顯,寶駿310是很能夠代表“年輕人的第一台車”的,不僅因為其低得讓人詫異的售價,更因為其自身擁有着匹配於年輕人經濟能力的用車成本。對於購買寶駿310的消費者來說,使用寶駿310是一種毫無壓力的出行方式,而不是給自身帶來額外的經濟壓力。而這正是現時許多年輕人所真真切切需要的。

吃一頓賣相稍好的快餐,買一張講求緣分的打折電影票,買一件摻雜着劣質尼龍面料的T恤。而現在,同等的價值你可以選擇駕駛寶駿310去各種你想去的地方,這才是年輕人該做的事。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

還值得大家加價提車嗎?神車SUV途觀的評測來了!

更綿密的齒比、更迅捷的換擋速度,新途觀(進口)的行駛舒適性得到極大提高。除了1、2擋輕微頓挫所帶來的瑕疵,它換擋的平順性很容易征服那些鍾情CVT車型的消費者,對動力的傳遞也遠比傳統的自動變速箱更加直接。無論你需要哪一種動力響應,這台變速箱都能夠把你伺候妥帖。

駕車從深南大道的洲際酒店駛出,一路沿途上都收穫了不少關注的目光。試駕活動參加過不少,這樣的情況並不多見。當然,放在新途觀(進口)上,這也是合理的一件事。

對於一款持續數年時間需要加價銷售的SUV來說,途觀本身就要比翼虎、奇駿這類的美日系車型更能博得人們的關注。甚至比起更高一級的寶馬X1、奧迪Q3等德系豪華車型,途觀也屬有恃無恐的那一類。所以當我開着新途觀(進口)在熙然的深圳街頭穿梭,那是一個讓人驚艷的畫面。

▼純正的歐洲風味

我手上的這台新途觀(進口)剛從德國運抵中國,便被送到我的手上,一方面它保持着最初始的出廠設定(甚至胎壓也被設置為船運胎壓),而另一方面它保持了最純正的歐洲風格調校。

也正因如此,新途觀(進口)的底盤調校要比市面上的SUV車型都要硬朗更多,比起現款的國產途觀更甚。不以舒適為導向的懸挂很願意把路面的每一個細節毫無保留地傳遞給駕駛者。若是遇到減速帶這類起伏較大的障礙,那乾脆的蹦蹦聲響能夠清晰地體現出懸挂中後段的硬氣。更多的時候,我們耍起了凌波微步,以求躲閃深圳街頭那永遠也修不好的坑窪。大眾的工程師告訴我,國產後這一情況會被改善的。

完全按照歐洲人口味打造的新途觀(進口)並沒有在深圳街頭找到它拳腳的空間,於是我們把新途觀(進口)開上了廣深沿江高速。修繕較好的四車道以及120公里的限速,這更符合歐洲人的駕駛環境。新途觀(進口)並沒有讓人失望:高速巡航的穩定性並不亞於漢蘭達這樣的中大型SUV;快速變換車道,也沒有搖曳的姿態。事實上,新途觀(進口)的行駛穩定性足夠讓你捨棄選擇轎車。再加上帶方向糾正的車道偏離預防功能,在高速上鬆開方向盤已經不再值得炫耀。

▼7.7秒的爽快

作為基於大眾最新MQB平台誕生的新途觀(進口),擁有1.4T(EA211)、2.0T(第三代EA888 低功率)、2.0T(第三代 EA888 高功率)三種動力,按照大眾的動力標識分別為280 TSI、330 TSI、380 TSI。其中280 TSI版本搭載的是大家所熟悉的DQ250 6速雙離合變速箱,而330 TSI以及380 TSI則搭載大眾最新的DQ500 7速雙離合變速箱。

我開的是330 TSI版本的頂配車型,180ps的最大馬力在4000-6000rpm間輸出,320Nm的最大扭矩在1500-3940rpm間輸出,配備了四輪驅動。官方的百公里加速成績為7.7s,與上代的2.0TSI車型的9.9s相比,是一個長足的進步。

新途觀(進口)依舊把動力的充分響應隱藏在油門踏板的中後段,比起一眾的日韓系對手,它循序漸進的提速感更讓人安心。但有必要強調的是:新途觀(進口)的動力輸出要比上代車型進取太多。尤其在中低速情況下,轉速指針已經不再需要頻繁突破2000rpm。2000rpm以下,動力的表現已經游刃有餘。而中高速的提速,爽快利落便足夠形容。

▼連降4擋的瘋狂

這當中,DQ500 7速雙離合變速箱貢獻了不少的功勞。經過離合器和齒輪箱的整體優化,這款變速箱已經能夠承受最大600Nm的扭矩。而更重要的是,這台變速箱在帶來更多擋位的情況下,實現了更小的體積。當然,這台變速箱更早便已經被運用在大眾集團旗下的奧迪車型上,購買新途觀(進口)的消費者不是第一個吃螃蟹的人。

顯然,用上DQ500 7速雙離合變速箱是一個明智決定。更綿密的齒比、更迅捷的換擋速度,新途觀(進口)的行駛舒適性得到極大提高。除了1、2擋輕微頓挫所帶來的瑕疵,它換擋的平順性很容易征服那些鍾情CVT車型的消費者,對動力的傳遞也遠比傳統的自動變速箱更加直接。無論你需要哪一種動力響應,這台變速箱都能夠把你伺候妥帖。而更吸引我的,是大腳油門下變速箱連降4擋的瘋狂。沒有遲疑,轉速指針能夠瞬間從1500rpm的位置瞬間陡升到5000rpm的位置,180ps的動力噴薄而出。

▼開途觀去劈山吧

我們決定把新途觀(進口)拉到大梅沙附近的山上好拍些照片,順便更好地在山路間檢驗它身上的歐洲風味。我試駕的330 TSI頂配車型配備了舒適、經濟、標準、運動、個性五種駕駛模式,同時還擁有雪地、公路、越野、個性越野的路況選擇功能。通過換擋桿旁的旋鈕可以選擇駕駛員所適合的駕駛模式。

我把駕駛模式設置在了運動模式上,明顯感覺到的是DCC底盤動態控制被調至更緊繃的狀態,發動機轉速也以2000rpm作為起步的基準,而方向盤則變得更為粘手以及沉實。在狹窄陡斜的山道,更晚的剎車、更大的轉彎角度、更大的出彎油門。硬朗的懸挂支撐加上四輪驅動系統的加持,新途觀(進口)對車身的控制與我判斷的一樣:紮實、靈活。新途觀(進口)提供了很多SUV都不所具備的運動。在老款途觀車主的角度看來,這運動得有點不可思議。當然,輪胎的緣故以及龐大的車身尺寸,仍然無法使得新途觀(進口)達到翼虎這種主打運動操控的SUV的表現。但是,運動得足夠了。

▼大眾精神的捍衛者

在山的某個角落,我終於有了停下的機會好好端詳這輛從彼岸遠渡而來的新途觀(進口)。上代車型的圓潤元素被完全抽離了,新途觀(進口)的身上被大量橫七豎八的直線線條所佔據,這是大眾未來的設計風格,全新邁騰、輝昂都有着這樣的濃烈味道。而為了彰顯出SUV的身份,橫向的中網被設計為立體感更強的盔甲狀,發動機蓋上的V型輪廓線條被打造得異常明顯,霧燈區域配備了強調運動化的黑色空氣套件。新途觀(進口)的硬朗風格大大超越了它的同門兄弟們。

側面依然是大眾喜歡炫耀製造工藝的舞台。兩條清晰立體的小倒角特徵線由前車門處貫穿至車尾。門把、尾燈被特徵線所連接,除了設計上的大膽,精準的生產工藝也屬必不可少的要素。德系車一貫的工藝精緻感仍然是最大的表現。而尾部則比側面更強調立體感,F狀的尾燈造型以及切割狀的牌照區域都讓人能夠在第一眼辨析出新途觀(進口)的獨特性。

其次,把新途觀(進口)的車身尺寸變大,成了大眾工程師的一項工作重點。新途觀(進口)長寬高達到4490mm/1859mm/1657mm,軸距為2681mm。除了高度要比上代車型降低46mm,其餘數據均有了跨越性的增長。至此,新途觀(進口)在軸距尚未加長的情況下,已經有了足夠的空間表現。

至於內飾上的設計,新途觀(進口)全面向最新的家族式設計看齊。可玩度頗高的12.3寸的数字化儀錶盤、8寸的多媒體中控屏、平直的線條設計,都足夠讓人熟悉。當然,這些都不如人機設計的改善更能討好我。車窗升降控制鍵回歸至傳統的位置;方向盤採用了更好感知角度的平底方向盤;前排座椅靠背上增設了可摺疊的小桌板。這一代的途觀(進口)變得容易親近了許多。

比起上一代的車型,這一代的途觀進化了太多。它更富有時代性,它也更能滿足年輕消費者對於運動性的需求,它匯聚了大眾最新、最具有代表性的設計以及技術。它着實驚艷到了我。或許你會抱怨它的底盤太硬,或許你會抱怨它的售價太貴,但相信我,它比BBA矚目多了。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

※網頁設計最專業,超強功能平台可客製化

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

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!