Giới thiệu
Optimize code thường không phải ưu tiên hàng đầu đối với người dùng R. Vì bản thân R không phải là ngôn ngữ ưu tiên cho tốc độ xử lý.
Hơn nữa, nếu có máy khỏe, thì người dùng thường không quan tâm tới việc tối ưu code lắm. Nhưng với những mày cùi hoặc làm việc với dữ liệu tương đối lớn thì việc tối ưu code là việc rất cần thiết.
Để tối ưu được code thì coder phải xác định được đâu làm điểm nghẽn (bottlenecks). May mắn là có package profvis hỗ trợ rất tốt việc này.
Package này chỉ ra đoạn code nào chạy mất nhiều thời gian. Sau đó, việc còn lại là optimize, bằng cách viết lại code hoặc dùng các functions tương tự
Thực hành
Đầu tiên cứ lên google search package profvis trước rồi làm theo hướng dẫn
https://www.r-bloggers.com/optimising-your-r-code-a-guided-example/ https://rstudio.github.io/profvis/examples.html
library(profvis)
# Generate data
times <- 4e5
cols <- 150
data <- as.data.frame(x = matrix(rnorm(times * cols, mean = 5), ncol = cols))
data <- cbind(id = paste0("g", seq_len(times)), data)
profvis({
data1 <- data # Store in another variable for this run
# Get column means
means <- apply(data1[, names(data1) != "id"], 2, mean)
# Subtract mean from each column
for (i in seq_along(means)) {
data1[, names(data1) != "id"][, i] <- data1[, names(data1) != "id"][, i] - means[i]
}
})
Đầu tiên, data.frame có 151 dòng, 1 cột là id và 150 cột là numeric variables, đoạn code kia đơn giản là tính giá trị trung bình của 150 cột numeric, và centering từng cột 1
Nhìn tab flame graph nhìn ra ngay thủ phạm là đoạn code nào làm chậm. Trong ví dụ trên là đoạn means <- apply(data1[, names(data1) != “id”], 2, mean) là chậm nhất.
Tiếp theo nhìn sang tab data, tiếp tục nhìn ra nguyên nhân chậm là do lệnh apply. Và hàm này gọi 2 hàm as.matrix và aperm. 2 hàm này chuyển data.frame thành matrix và transpose chúng.
Có 2 vấn đề đó nên chúng ta sẽ xử lý bằng cách chuyển data.frame thành matrix trước và transpose (nhưng chú ý nếu transpose làm cho mất ID). Thay vì dùng hàm apply ta có thể dùng hàm colMeans, hoặc dùng lapply, vapply hay map
Ví dụ:
library(purrr)
profvis({
data1 <- data
# Four different ways of getting column means
means <- apply(data1[, names(data1) != "id"], 2, mean)
means <- colMeans(data1[, names(data1) != "id"])
means <- lapply(data1[, names(data1) != "id"], mean)
means <- vapply(data1[, names(data1) != "id"], mean, numeric(1))
means <- map(data1[, names(data1) != "id"], mean)
})
Nhìn đồ thị thấy hàm lapply và map là tốt nhất, vì vậy có thể chọn 1 trong 2 hàm này để thay thế vào đoạn code
LS0tDQp0aXRsZTogIlVzaW5nIHByb2Z2aXMgdG8gb3B0aW1pemUgY29kZSINCmF1dGhvcjogIk5ndXnhu4VuIE5n4buNYyBCw6xuaCINCmRhdGU6ICIyMSBKVU4gMjAxOSINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIEdp4bubaSB0aGnhu4d1DQoNCk9wdGltaXplIGNvZGUgdGjGsOG7nW5nIGtow7RuZyBwaOG6o2kgxrB1IHRpw6puIGjDoG5nIMSR4bqndSDEkeG7kWkgduG7m2kgbmfGsOG7nWkgZMO5bmcgUi4gVsOsIGLhuqNuIHRow6JuIFIga2jDtG5nIHBo4bqjaSBsw6AgbmfDtG4gbmfhu68gxrB1IHRpw6puIGNobyB04buRYyDEkeG7mSB44butIGzDvS4NCg0KSMahbiBu4buvYSwgbuG6v3UgY8OzIG3DoXkga2jhu49lLCB0aMOsIG5nxrDhu51pIGTDuW5nIHRoxrDhu51uZyBraMO0bmcgcXVhbiB0w6JtIHThu5tpIHZp4buHYyB04buRaSDGsHUgY29kZSBs4bqvbS4gTmjGsG5nIHbhu5tpIG5o4buvbmcgbcOgeSBjw7lpIGhv4bq3YyBsw6BtIHZp4buHYyB24bubaSBk4buvIGxp4buHdSB0xrDGoW5nIMSR4buRaSBs4bubbiB0aMOsIHZp4buHYyB04buRaSDGsHUgY29kZSBsw6Agdmnhu4djIHLhuqV0IGPhuqduIHRoaeG6v3QuDQoNCsSQ4buDIHThu5FpIMawdSDEkcaw4bujYyBjb2RlIHRow6wgY29kZXIgcGjhuqNpIHjDoWMgxJHhu4tuaCDEkcaw4bujYyDEkcOidSBsw6BtIMSRaeG7g20gbmdo4bq9biAoKipib3R0bGVuZWNrcyoqKS4gTWF5IG3huq9uIGzDoCBjw7MgcGFja2FnZSBwcm9mdmlzIGjhu5cgdHLhu6MgcuG6pXQgdOG7kXQgdmnhu4djIG7DoHkuDQoNClBhY2thZ2UgbsOgeSBjaOG7iSByYSDEkW/huqFuIGNvZGUgbsOgbyBjaOG6oXkgbeG6pXQgbmhp4buBdSB0aOG7nWkgZ2lhbi4gU2F1IMSRw7MsIHZp4buHYyBjw7JuIGzhuqFpIGzDoCBvcHRpbWl6ZSwgYuG6sW5nIGPDoWNoIHZp4bq/dCBs4bqhaSBjb2RlIGhv4bq3YyBkw7luZyBjw6FjIGZ1bmN0aW9ucyB0xrDGoW5nIHThu7ENCg0KIyBUaOG7sWMgaMOgbmgNCg0KxJDhuqd1IHRpw6puIGPhu6kgbMOqbiBnb29nbGUgc2VhcmNoIHBhY2thZ2UgKipwcm9mdmlzKiogdHLGsOG7m2MgcuG7k2kgbMOgbSB0aGVvIGjGsOG7m25nIGThuqtuDQoNCmh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL29wdGltaXNpbmcteW91ci1yLWNvZGUtYS1ndWlkZWQtZXhhbXBsZS8NCmh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vcHJvZnZpcy9leGFtcGxlcy5odG1sDQoNCmBgYHtyfQ0KbGlicmFyeShwcm9mdmlzKQ0KYGBgDQoNCg0KYGBge3J9DQojIEdlbmVyYXRlIGRhdGENCnRpbWVzIDwtIDRlNQ0KY29scyA8LSAxNTANCmRhdGEgPC0gYXMuZGF0YS5mcmFtZSh4ID0gbWF0cml4KHJub3JtKHRpbWVzICogY29scywgbWVhbiA9IDUpLCBuY29sID0gY29scykpDQpkYXRhIDwtIGNiaW5kKGlkID0gcGFzdGUwKCJnIiwgc2VxX2xlbih0aW1lcykpLCBkYXRhKQ0KDQpwcm9mdmlzKHsNCiAgZGF0YTEgPC0gZGF0YSAgICMgU3RvcmUgaW4gYW5vdGhlciB2YXJpYWJsZSBmb3IgdGhpcyBydW4NCg0KICAjIEdldCBjb2x1bW4gbWVhbnMNCiAgbWVhbnMgPC0gYXBwbHkoZGF0YTFbLCBuYW1lcyhkYXRhMSkgIT0gImlkIl0sIDIsIG1lYW4pDQoNCiAgIyBTdWJ0cmFjdCBtZWFuIGZyb20gZWFjaCBjb2x1bW4NCiAgZm9yIChpIGluIHNlcV9hbG9uZyhtZWFucykpIHsNCiAgICBkYXRhMVssIG5hbWVzKGRhdGExKSAhPSAiaWQiXVssIGldIDwtIGRhdGExWywgbmFtZXMoZGF0YTEpICE9ICJpZCJdWywgaV0gLSBtZWFuc1tpXQ0KICB9DQp9KQ0KYGBgDQoNCsSQ4bqndSB0acOqbiwgZGF0YS5mcmFtZSBjw7MgMTUxIGTDsm5nLCAxIGPhu5l0IGzDoCBpZCB2w6AgMTUwIGPhu5l0IGzDoCBudW1lcmljIHZhcmlhYmxlcywgxJFv4bqhbiBjb2RlIGtpYSDEkcahbiBnaeG6o24gbMOgIHTDrW5oIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggY+G7p2EgMTUwIGPhu5l0IG51bWVyaWMsIHbDoCBjZW50ZXJpbmcgdOG7q25nIGPhu5l0IDEgDQoNCk5ow6xuIHRhYiBmbGFtZSBncmFwaCBuaMOsbiByYSBuZ2F5IHRo4bunIHBo4bqhbSBsw6AgxJFv4bqhbiBjb2RlIG7DoG8gbMOgbSBjaOG6rW0uIFRyb25nIHbDrSBk4bulIHRyw6puIGzDoCDEkW/huqFuICoqbWVhbnMgPC0gYXBwbHkoZGF0YTFbLCBuYW1lcyhkYXRhMSkgIT0gImlkIl0sIDIsIG1lYW4pKiogbMOgIGNo4bqtbSBuaOG6pXQuDQoNClRp4bq/cCB0aGVvIG5ow6xuIHNhbmcgdGFiIGRhdGEsIHRp4bq/cCB04bulYyBuaMOsbiByYSBuZ3V5w6puIG5ow6JuIGNo4bqtbSBsw6AgZG8gbOG7h25oICoqYXBwbHkqKi4gVsOgIGjDoG0gbsOgeSBn4buNaSAyIGjDoG0gKmFzLm1hdHJpeCogdsOgICphcGVybSouIDIgaMOgbSBuw6B5IGNodXnhu4NuIGRhdGEuZnJhbWUgdGjDoG5oIG1hdHJpeCB2w6AgdHJhbnNwb3NlIGNow7puZy4gDQoNCkPDsyAyIHbhuqVuIMSR4buBIMSRw7MgbsOqbiBjaMO6bmcgdGEgc+G6vSB44butIGzDvSBi4bqxbmcgY8OhY2ggY2h1eeG7g24gZGF0YS5mcmFtZSB0aMOgbmggbWF0cml4IHRyxrDhu5tjIHbDoCB0cmFuc3Bvc2UgKG5oxrBuZyBjaMO6IMO9IG7hur91IHRyYW5zcG9zZSBsw6BtIGNobyBt4bqldCBJRCkuIFRoYXkgdsOsIGTDuW5nIGjDoG0gYXBwbHkgdGEgY8OzIHRo4buDIGTDuW5nIGjDoG0gY29sTWVhbnMsIGhv4bq3YyBkw7luZyBsYXBwbHksIHZhcHBseSBoYXkgbWFwIA0KDQpWw60gZOG7pToNCg0KYGBge3J9DQpsaWJyYXJ5KHB1cnJyKQ0KYGBgDQoNCg0KYGBge3J9DQpwcm9mdmlzKHsNCiAgZGF0YTEgPC0gZGF0YQ0KICAjIEZvdXIgZGlmZmVyZW50IHdheXMgb2YgZ2V0dGluZyBjb2x1bW4gbWVhbnMNCiAgbWVhbnMgPC0gYXBwbHkoZGF0YTFbLCBuYW1lcyhkYXRhMSkgIT0gImlkIl0sIDIsIG1lYW4pDQogIG1lYW5zIDwtIGNvbE1lYW5zKGRhdGExWywgbmFtZXMoZGF0YTEpICE9ICJpZCJdKQ0KICBtZWFucyA8LSBsYXBwbHkoZGF0YTFbLCBuYW1lcyhkYXRhMSkgIT0gImlkIl0sIG1lYW4pDQogIG1lYW5zIDwtIHZhcHBseShkYXRhMVssIG5hbWVzKGRhdGExKSAhPSAiaWQiXSwgbWVhbiwgbnVtZXJpYygxKSkNCiAgbWVhbnMgPC0gbWFwKGRhdGExWywgbmFtZXMoZGF0YTEpICE9ICJpZCJdLCBtZWFuKQ0KfSkNCmBgYA0KDQoNCk5ow6xuIMSR4buTIHRo4buLIHRo4bqleSBow6BtIGxhcHBseSB2w6AgbWFwIGzDoCB04buRdCBuaOG6pXQsIHbDrCB24bqteSBjw7MgdGjhu4MgY2jhu41uIDEgdHJvbmcgMiBow6BtIG7DoHkgxJHhu4MgdGhheSB0aOG6vyB2w6BvIMSRb+G6oW4gY29kZQ0KDQoNCg==