swizard ([info]swizard) wrote,

И еще раз о задачке про ip-диапазоны.

Собственно, описание задачи и варианты её решения можно почитать здесь у [info]nponeccop. Один из вариантов там предложен на CL лавсанчиком. Собственно, он меня немножечко возмутил, поэтому пришлось писать этот пост :)

Суть токова: это вполне себе рабочий код, но так на common lisp писать не надо :) Потому что так надо писать на си. На сях этот же код будет вдвое короче (хотя бы за счет отсутствия скобок и более лаконичного синтаксиса) и вдвое быстрее.

Писать руками такую простыню низкоуровнего кода на CL -- это явный провал. На лиспе надо писать программу, которая будет генерировать низкоуровневый код, это ежу понятно.

Условия нам благоприятсвуют: диапазоны грузятся один раз, а дальше только лукап. Поэтому мы попробуем решить эту задачу классическим лисповым способом: «расставить скобки вокруг спецификации и заставить её запуститься». Вот прямо так, буквально. Итак, имеем файл ranges.list такого вида:

104.72.221.173,220.57.219.35
16.65.26.150,133.42.154.151
80.241.37.220,93.109.90.13
35.165.212.97,105.166.11.16
122.143.149.115,246.17.13.31
20.44.170.80,144.105.12.169
122.132.114.84,184.165.60.95
102.111.151.45,120.152.236.26
53.252.70.24,171.51.24.110
101.103.12.180,224.55.178.136

Отлично, давайте расставим вокруг скобки, чтобы получить ranges.list.lisp:

(in-package :ip-ranges)
(gen-test-proc
  "104.72.221.173,220.57.219.35"
  "16.65.26.150,133.42.154.151"
  "80.241.37.220,93.109.90.13"
  "35.165.212.97,105.166.11.16"
  "122.143.149.115,246.17.13.31"
  "20.44.170.80,144.105.12.169"
  "122.132.114.84,184.165.60.95"
  "102.111.151.45,120.152.236.26"
  "53.252.70.24,171.51.24.110"
  "101.103.12.180,224.55.178.136"
)

Только, конечно же, не руками, а вот так:

(defun preprocess-ranges-file (filename)
  (let ((lisp-file (format nil "~a.lisp" filename)))
    (with-open-file (f-in filename)
      (with-open-file (f-out lisp-file
                             :direction :output
                             :if-exists :supersede
                             :if-does-not-exist :create
                             :external-format :ascii)
        (format f-out "(in-package :ip-ranges)~%(gen-test-proc~%")
        (iter (for range in-stream f-in using #'read-line)
              (format f-out "  \"~a\"~%" range))
        (format f-out ")~%~%")))
    lisp-file))

Переходим ко второму этапу: надо заставить полученную скобочную спецификацию компилироваться. Давайте сначала быстренько распарсим строчку ip-адреса и диапазона, плюнем на перфоманс:

(defpackage #:ip-ranges
  (:use :cl :iterate :metatilities)
  (:shadowing-import-from :metatilities #:minimize #:finish)
  (:export #:check))

(in-package :ip-ranges)

(defun extract-values (string)
  (unless (zerop (length string))
    (multiple-value-bind (value rest-index)
        (parse-integer string :junk-allowed t)
      (if value
          (cons value (extract-values (subseq string rest-index)))
          (extract-values (subseq string 1))))))

Работать это должно так:

IP-RANGES> (extract-values "104.72.221.173,220.57.219.35")
(104 72 221 173 220 57 219 35)

Теперь надо немного пораскинуть мозгами. Вот у нас есть диапазон, заданный ip-адресами, каждый из которых представлен четырьмя октетами -- в нашем случае '(104 72 221 173) и '(220 57 219 35). Допустим, нам выдали адрес в таком же формате: ip = (list ip-0 ip-1 ip-2 ip-3); какой код должен быть в программе, которым можно проверить принадлежность этого адреса заданному диапазону? Собственно, тут даже не надо ничего делать руками (например, склеивать октеты в 32-х битный адрес и т.п.), просто тупо сгенерируем сравнение в лесенкой столбик :)

(defun ip-range (range-string)
  (let ((values (extract-values range-string))
        (vars '(ip-0 ip-1 ip-2 ip-3)))
    (assert (= (length values) 8))
    (labels ((stairs (cmp values vars)
               (if (null values)
                   t
                   `(or (,cmp ,(car vars) ,(car values))
                        (and (= ,(car vars) ,(car values))
                             ,(stairs cmp (cdr values) (cdr vars)))))))
      `(and ,(stairs '> (subseq values 0 4) vars)
            ,(stairs '< (subseq values 4 8) vars)))))

IP-RANGES> (ip-range "104.72.221.173,220.57.219.35")
(AND
 (OR (> IP-0 104)
     (AND (= IP-0 104)
          (OR (> IP-1 72)
              (AND (= IP-1 72)
                   (OR (> IP-2 221)
                       (AND (= IP-2 221)
                            (OR (> IP-3 173) (AND (= IP-3 173) T))))))))
 (OR (< IP-0 220)
     (AND (= IP-0 220)
          (OR (< IP-1 57)
              (AND (= IP-1 57)
                   (OR (< IP-2 219)
                       (AND (= IP-2 219)
                            (OR (< IP-3 35) (AND (= IP-3 35) T)))))))))

Ну а теперь у нас есть все для того, чтобы "скомпилировать спецификацию":

(defmacro gen-test-proc (&rest ranges)
  `(defun ip-check (ip-0 ip-1 ip-2 ip-3)
     (or ,@(mapcar #'ip-range ranges))))

(defun check (ip-string)
  (apply #'ip-check (extract-values ip-string)))

Вуаля:

IP-RANGES> (load (compile-file (preprocess-ranges-file #p"ranges.list")))
; compiling file "/home/swizard/devel/lisp/ip-ranges/ranges.list.lisp" (written 03 NOV 2011 11:16:02 PM):
; compiling (IN-PACKAGE :IP-RANGES)
; compiling (GEN-TEST-PROC "104.72.221.173,220.57.219.35" ...)

; /home/swizard/devel/lisp/ip-ranges/ranges.list.fasl written
; compilation finished in 0:00:00.045
T
IP-RANGES> (check "192.168.0.1")
T
IP-RANGES> (check "10.0.0.0")
NIL


Итак, еще раз, что мы сейчас сделали:
  • Расставили скобки вокруг списка ip-диапазонов.
  • Сгенерировали по описанию функцию ip-check, решающую задачу.
  • ...
  • Profit!

Красотища? Да, но пока что не особо.

  • Несмотря на константные проверки и восьмибитную сегментацию адреса, у нас получилась последовательная проверка.
  • Несмотря на автоматическую генерацию условия по диапазону, это условие генерируется какое-то кривоватое и сильно избыточное.
  • Несмотря на то, что какие-то диапазоны проверять нет смысла, так как они "поглощаются" более широкими, все равно проверяются все.
  • По условиям задачи диапазонов могут быть сотни: поэтому код надо сегментировать по функциям, чтобы не нагнуть компилятор при стратегии (optimize (speed 3))


Собственно, все это я выношу в следующий пост: суперкомпиляция условий, сегментация и препроцессинг кода и так далее. В идеале мы должны не только решить задачу, а еще и получить самый производительный код.
Tags: article, code, code generation, common lisp, dsl, ip, ip range, lisp

  • Post a new comment

    Error

    Your IP address will be recorded 

  • 16 comments

[info]rigidus

November 3 2011, 19:56:16 UTC 6 months ago

Ура! Я ждал этого поста и теперь жду продолжение

[info]swizard

November 3 2011, 21:49:23 UTC 6 months ago

Про суперкомпиляцию я уже написал :)

[info]thesz

November 3 2011, 20:10:39 UTC 6 months ago

Я вот, что думаю.

Насколько сложно расставить скобки вокруг спецификации "симулятор VHDL"?

[info]swizard

November 3 2011, 21:52:09 UTC 6 months ago

Честно -- я не знаю, не специалист в VHDL :)

Скобки-то расставить элементарно. А вот заставить все это взлететь, подозреваю, что сложно.

[info]thesz

November 3 2011, 21:52:57 UTC 6 months ago

Ага. То есть, скобки можно опустить? ;)

[info]swizard

November 3 2011, 22:00:48 UTC 6 months ago

Ну конечно. Просто расставить скобки намного удобней, чем писать отдельный парсер.

[info]thesz

November 3 2011, 22:02:07 UTC 6 months ago

А вот если язык скобки позволяет опустить, он удобней в плане компиляции спецификации?

[info]swizard

November 3 2011, 22:17:45 UTC 6 months ago

Я не очень понимаю, что ты имеешь в виду. Все равно тебе нужно спецификацию либо сразу писать в синтаксисе целевого языка, либо как-то трансформировать в некий AST, с которым ты дальше будешь работать. Разумеется, это можно сделать на любом языке общего назначения.

Расстановка скобок просто позволяет не заморачиваться написанием своего парсера, если изначально спецификация задана в чужом синтаксисе.

[info]thesz

November 3 2011, 22:23:00 UTC 6 months ago

А вот если mixfix, то нужны ли скобки?

[info]swizard

November 3 2011, 22:33:12 UTC 6 months ago

А ты сам-то как думаешь? :)

[info]thesz

November 4 2011, 09:38:08 UTC 6 months ago

Хорошо.

Насколько больше понадобится скобок?

[info]13-49-ru.blogspot.com

November 4 2011, 01:22:13 UTC 6 months ago

А что такое "спецификация ""симулятора VHDL"""?

[info]thesz

November 4 2011, 09:38:59 UTC 6 months ago

Если уж это вопрос, то как вокруг неё ставить скобки?

[info]13-49-ru.blogspot.com

November 4 2011, 12:38:24 UTC 6 months ago

На самом деле, г-н swizard в вопросе расставления скобок вокруг всего подряд скрывает часть правды: нужны ещё и двоеточия вначале примерно каждого второго терма.

[info]cvet_divanoff

November 4 2011, 09:30:01 UTC 6 months ago

Скобки вокруг симуляции даже MSC расставить удалось далеко не сразу, а уж сам vhdl богаче msc в триллиард раз, я думаю.

[info]13-49-ru.blogspot.com

November 4 2011, 12:58:10 UTC 6 months ago

EDIF
Create an Account
Forgot your login or password?
Facebook Twitter More login options
English • Español • Deutsch • Русский…