Tag: performance

  • Java Vector vs Arraylist: The Performance

    Java Vector vs Arraylist: The Performance

    Chắc hẳn anh em làm việc với Java sẽ quen thuộc với VectorArrayList. Cả hai đều implement interface List và có cấu trúc dữ liệu dạng dynamically resizeable arrays, giống như một mảng thông thường.

    Cốt thì đơn giản như sau

    ArrayList<T> al = new ArrayList<T>();
    Vector<T> v = new Vector<T>();
    

    Tuy nhiên thì vẫn có những điểm khác nhau giữa VectorArrayList:

    • Synchronization: Về cơ bản Vector Synchronized, tức là tại một thời điểm, một và chỉ 1 thread có thể sử dụng Vector, trong khi đó Array List thì không, và nhiều thread có thể làm việc trên cùng một Array List. Ví dụ: một thread có thể thực hiện việc thêm (add), và một thread khác có thể thực hiện việc remove ở trong một môi trường multithreading.

      Nếu nhiều thread truy cập mảng Array List đồng thời, thì chúng ta phải synchronize code block nếu block đó sửa cấu trúc(thêm hoặc xóa (các) phần tử). Re-assign value không làm thay đổi cấu trúc. Nói cách khác thì Vector thread-safe còn Array List sẽ đạt được thread-safe bằng synchronized.

    • Data Growth: Cả hai đều có thể quản lý dữ liệu động tức là ** grow and shrink dynamically** và tối ưu hoá việc sử dụng bộ nhớ. Khác biệt lớn nhất là size, cả 2 đều có capacity tức là số lượng phần tử tối da tại một thời điểm, và mặc định là 10 (kể cả khi cả 2 đều rỗng)

    ArrayList() Constructs an empty list with an initial capacity of ten. https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

    Vector() Constructs an empty vector so that its internal data array has size 10 and its standard capacity increment is zero. https://docs.oracle.com/javase/7/docs/api/java/util/Vector.html

    Vấn đề là capacity của ArrayList sẽ tăng 50% khi số lượng phần tử vượt quá capacity còn vector là 100%

    • Traversal: Vector có thể sử dụng cả EnumerationIterator để traversing trong khi ArrayList chỉ có thể sử dụng Iterator,
    • Performance: Về cơ bản thì Array List sẽ nhanh hơn, vì nó non-synchronized, còn Vector thì synchronized(thread-safe). Nếu một thread hoạt động trên một Vector, thì nó sẽ có lock, các thread khác sẽ cần chờ lock để làm việc.

    Đo Vector vs ArrayList

    Nói chung là cần test, mình đã chuẩn bị một bài test cho ArrayList và Vector, cốt ở đây nhé. Bài này mình sử dụng OpenJDK JMH để thực hiện benchmark và thực hiện ở 2 bài toán: add và traversal. Đây là kết quả.

    Về cơ bản thì ArrayList mạnh hơn ở cả 2 bài test vì non-thread-safe. Tuy nhiên để cân nhắc chúng ta sẽ thấy:

    • Với bài toán single-thread, ArrayList là một lựa chọn hiển nhiên, nhưng với multithreading rõ ràng Vector có sự ưu tiên hơn.
    • Nếu chúng ta không biết hoặc không áng chừng được lượng data trong Collection, nhưng biết được tốc độ tăng của lượng dữ liệu, Vector lợi thế hơn, vì với Vector chúng ta có thể quản lý được increment value của capacity.
    • Vector là một legacy class, ArrayList thì mới hơn nên nhanh hơn 🙂
  • Optimize Lambda function with Nodejs.

    Optimize Lambda function with Nodejs.

    Gần đây có nghiên cứu lại mấy vấn đề của Lambda function và mày mò vào Node Summit, bài viết này thực ra trình bày lại topic này của Matt Lavin

    Về cơ bản với Lambda, AWS đã làm gần hết mọi thứ về management, scale function, kết nối đến các service như DynamoDB, SQS,… Gần như chúng ta chỉ cần chú ý đến việc coding là chính. Tuy nhiên để mọi thứ tốt hơn cho người dùng thì cần giảm latency, response nhanh hơn và dễ debug hơn trong những trường hợp cần thiết và chính trong source code Lambda function tức là:

    • Cải tiện latency
    • Tìm ra bug performance
    • Debug.

    Bài này sẽ nói về các cách optimze coding là chính, những phần khác thì hãy xem kỹ topic nhé.

    Đầu tiên bao giờ cũng cần tìm hiểu xem Lambda hoạt động như nào nhưng trước tiên mình sẽ đưa một ví dụ điển hình về lambda function:

    const dynamodb = require('aws-sdk/clients/dynamodb');
    const docClient = new dynamodb.DocumentClient();
    const tableName = process.env.SAMPLE_TABLE;
    exports.getByIdHandler = async (event) => {
        const { httpMethod, path, pathParameters } = event;
        if (httpMethod !== 'GET') {
            throw new Error(`Unsupported method`);
        }
        console.log('received:', JSON.stringify(event));
        const { id } = pathParameters;
        const params = {
            TableName: tableName,
            Key: { id },
        };
        const { Item } = await docClient.get(params).promise();
        const response = {
            statusCode: 200,
            body: JSON.stringify(Item),
        };
        return response;
    };
    

    Khá là điển hình với việc: Khởi tạo SDK, handle request, query database và đưa ra kết quả, tất nhiên trước đó sẽ là download source code và khởi chạy lambda function. Và hãy ghép nó vào mô hình lifecycle của lambda function như ở bên dưới.

    Như hình bên trên toàn bộ Lifecycle của AWS Lambda bao gồm Cold StartWarm Start. Warm start: bao gồm phần thời gian code chạy Cold start: thời gian chuẩn bị.

    Như vậy có thể thấy rằng phần warm start là phần coding đơn thuần và optimze như chúng ta optimze source code khi sử dụng các framework hay runtime khác. Mặt khác, mọi người thường nghĩ rằng Lambda Function sẽ thực hiện toàn bộ các bước trên mỗi lần execute nhưng không Lambda sẽ không khởi chạy lại Cold Start, miễn là bạn không update source code nhưng chỉ trong 15 phút thôi nhưng vậy là quá đủ. Reduce latency sẽ bắt đầu từ đây.

    Như hình trên cứ sau một khoảng thời gian nhất định Lambda function lại thực hiện Cold Start, những chỗ thời gian execute cao bất thường ấy, nhìn chung hãy để function Lambda luôn sẵn sàng để execute.

    Một cách chính thống hơn thì có thể tìm hiểu ở đây, AWS đề cập đến Lambda execution context (Môi trường để running Lambda code), context này sẽ bị đóng băng sau khi sử dụng xong function và được giã đông khi chạy lần tiếp và AWS cũng đề xuất một vài thủ thuật để optimize Lambda function:

    • Đầu tiên bắt đầu với handler method, Các object được khai báo bên ngoài handler vẫn được khởi tạo, cung cấp tối ưu hóa bổ sung khi handler được gọi lại. Ví dụ: nếu Lambda connect đến database (RDS, DynamoDB), thay vì kết nối đi kết nối lại, kết nối được tái sử dụng qua các lần invoke khác nhau trong một lambda instance. Một cách đơn giản có thể lazy load connection, như bây giờ AWS đã cải tiến SDK để dùng keep alive hoặc đơn giản là chuyễn những thứ nặng nề ra khỏi handler, cache lại AWS SDK Client
        const AWS = require('aws-sdk')
        // http or https
        const https = require('https');
        const agent = new https.Agent({
          keepAlive: true
        });
      
        const dynamodb = new AWS.DynamoDB({
          httpOptions: {
            agent
          }
        });
    
        fuction fuckingHeavyFunction() {
        }
    
        const outsideHeavyResult = fuckingHeavyFunction(); // run on every Lambda init instance.
    
        exports.handler = async (event) => {
          const heavyResult = fuckingHeavyFunction(); // run on every lambda request
          return response;
        };
    
    • Mỗi Lambda function có 512Mb lưu trữ ở /tmp, bạn có thể lưu trữ bất kỳ thứ gì. Vùng lưu trữ này sẽ được đóng băng cùng với Execution context, như vậy bạn có thể lưu trữ nhiều thứ ở trong này, ví dụ những tính toán, variable ít thay đổi có thể lưu trữ lại và dùng lại cho lần tiếp theo.
    • Nếu sử dụng background process trong Lambda function, hãy chắc chắn nó được hoàn toàn hoàn thành khi Lambda function kết thúc, vì có thể nó được sử dụng lại và tiếp tục chạy. Dẫn đến những bug không như ý. Nhưng nói chung cũng không nên nghĩ rằng Lambda sẽ sử dụng lại các tài nguyên khi chạy lại Lambda function, hãy chuẩn bị lại các tài nguyên hoặc kiểm tra việc sử dụng cho chắc chắn.